The IPAddress
class is not very friendly to serialization, as you’ve seen. Not only will it throw a SocketException
if you try to access the ScopeID
field for an IPv4 address, but it will also throw if you try to access the Address
field directly for an IPv6 address.
To get around the exceptions, you will need a custom JsonConverter
. A converter allows you to tell Json.Net exactly how you’d like it to serialize and/or deserialize a particular type of object. For an IPAddress
, it seems the easiest way to get the data that satisfies everyone is simply to convert it to its string representation and back. We can do that in the converter. Here is how I would write it:
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); } }
Pretty straightforward, as these things go. But, this is not the end of the story. If you need to go round-trip with your IPEndPoint
, then you will need a converter for it as well. Why? Because IPEndPoint
does not contain a default constructor, so Json.Net will not know how to instantiate it. Fortunately, this converter is not difficult to write either:
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); } }
So, now that we have the converters, how do we use them? Here is a simple example program that demonstrates. It first creates a couple of endpoints, serializes them to JSON using the custom converters, then immediately deserializes the JSON back into endpoints again using the same converters.
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); } } }
Here is the 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
-
Code of
WriteJson
can be simplified usingJObject
too. -
Performance impact of
WriteJson
andReadJson
can be improved by using the writer and reader objects, avoidingJObject
allocation. I have submitted an edit to this very useful answer.