2013-09-07 10 views
24

Sto cercando di serializzare un oggetto IPEndPoint con Json.Net e ottengo il seguente errore:Json.Net - Errore ottenere valore da 'IDAmbito' on 'System.Net.IPAddress'

Errore ottenere valore da ' ScopeId 'on' System.Net.IPAddress '.

La causa dell'errore è che sto utilizzando solo le proprietà IPV4 dell'oggetto IPAddress nell'endpoint. Quando il parser Json tenta di analizzare la porzione IPv6, accede alla proprietà ScopeID che genera un'eccezione socket "L'operazione tentata non è supportata per il tipo di oggetto referenziato" (Un valore null sarebbe sufficiente microsoft!)

I was chiedendo se ci può essere una soluzione alternativa per questo oltre a strappare tutto a parte e codificare le informazioni sull'indirizzo come una stringa? Ad un certo punto voglio supportare IPV6. C'è qualcosa che può essere fatto in Json.NET per ignorare l'errore o semplicemente NON tentare di serializzare lo ScopeID se la famiglia IPAddress è impostata su Internetwork invece di InternetworkIPV6?

Grazie,

Dinsdale

risposta

45

La classe IPAddress non è molto amichevole per la serializzazione, come avete visto. Non solo getterà un SocketException se si tenta di accedere al campo ScopeID per un indirizzo IPv4, ma verrà generato anche se si tenta di accedere al campo Address direttamente per un indirizzo IPv6.

Per aggirare le eccezioni, è necessario un numero personalizzato JsonConverter. Un convertitore ti permette di dire a Json.Net esattamente come ti piacerebbe serializzare e/o deserializzare un particolare tipo di oggetto. Per un IPAddress, sembra che il modo più semplice per ottenere i dati che soddisfano tutti è semplicemente convertirlo alla sua rappresentazione di stringa e viceversa. Possiamo farlo nel convertitore. Ecco come vorrei scriverlo:

class IPAddressConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return (objectType == typeof(IPAddress)); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     writer.WriteValue(value.ToString()); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     return IPAddress.Parse((string)reader.Value); 
    } 
} 

Piuttosto semplice, come vanno queste cose. Ma questa non è la fine della storia. Se devi fare il viaggio di andata e ritorno con il tuo IPEndPoint, allora avrai bisogno anche di un convertitore. Perché? Perché IPEndPoint non contiene un costruttore predefinito, quindi Json.Net non saprà come istanziarlo. Per fortuna, questo convertitore non è difficile scrivere uno:

class IPEndPointConverter : JsonConverter 
{ 
    public override bool CanConvert(Type objectType) 
    { 
     return (objectType == typeof(IPEndPoint)); 
    } 

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     IPEndPoint ep = (IPEndPoint)value; 
     JObject jo = new JObject(); 
     jo.Add("Address", JToken.FromObject(ep.Address, serializer)); 
     jo.Add("Port", ep.Port); 
     jo.WriteTo(writer); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     JObject jo = JObject.Load(reader); 
     IPAddress address = jo["Address"].ToObject<IPAddress>(serializer); 
     int port = (int)jo["Port"]; 
     return new IPEndPoint(address, port); 
    } 
} 

Quindi, ora che abbiamo i convertitori, come possiamo usarli? Ecco un semplice esempio di programma che dimostra. Prima crea un paio di endpoint, li serializza su JSON utilizzando i convertitori personalizzati, quindi deserializza immediatamente nuovamente il JSON negli endpoint utilizzando gli stessi convertitori.

public class Program 
{ 
    static void Main(string[] args) 
    { 
     var endpoints = new IPEndPoint[] 
     { 
      new IPEndPoint(IPAddress.Parse("8.8.4.4"), 53), 
      new IPEndPoint(IPAddress.Parse("2001:db8::ff00:42:8329"), 81) 
     }; 

     var settings = new JsonSerializerSettings(); 
     settings.Converters.Add(new IPAddressConverter()); 
     settings.Converters.Add(new IPEndPointConverter()); 
     settings.Formatting = Formatting.Indented; 

     string json = JsonConvert.SerializeObject(endpoints, settings); 
     Console.WriteLine(json); 

     var endpoints2 = JsonConvert.DeserializeObject<IPEndPoint[]>(json, settings); 

     foreach (IPEndPoint ep in endpoints2) 
     { 
      Console.WriteLine(); 
      Console.WriteLine("AddressFamily: " + ep.AddressFamily); 
      Console.WriteLine("Address: " + ep.Address); 
      Console.WriteLine("Port: " + ep.Port); 
     } 
    } 
} 

Ecco l'output:

[ 
    { 
    "Address": "8.8.4.4", 
    "Port": 53 
    }, 
    { 
    "Address": "2001:db8::ff00:42:8329", 
    "Port": 81 
    } 
] 

AddressFamily: InterNetwork 
Address: 8.8.4.4 
Port: 53 

AddressFamily: InterNetworkV6 
Address: 2001:db8::ff00:42:8329 
Port: 81 

Fiddle: https://dotnetfiddle.net/tK7NKY

+0

Codice di 'WriteJson' può essere semplificata utilizzando' JObject' troppo. – Athari

+0

Brillante. Grazie mille Brian! – Dinsdale

+0

Nessun problema; felice di aiutare. –