我正在尝试使用 Json.Net
序列化IPEndpoint
对象,但出现以下错误:
从
System.Net.IPAddress
上的ScopeId
获取值时出错。
错误的原因是我只在端点中使用IPAddress
对象的 IPV4 属性。当 Json 解析器尝试解析 IPv6 部分时,它会访问引发套接字异常的 ScopeID
属性
所引用的对象类型不支持尝试的操作"(空就足够了!
我想知道除了将所有内容拆开并将地址信息编码为字符串之外,是否有其他解决方法?在某些时候,我确实想支持IPV6。如果IPAddress
系列设置为 Internetwork
而不是 InternetworkIPV6
,是否可以 Json.NET 忽略错误或干脆不尝试序列化ScopeID
?
如您所见,IPAddress
类对序列化不是很友好。 如果您尝试访问 IPv4 地址的ScopeID
字段,它不仅会引发SocketException
,而且如果您尝试直接访问 IPv6 地址的Address
字段,它也会引发。
要绕过例外情况,您将需要一个 自定义JsonConverter
. 转换器允许您准确地告诉 Json.Net 您希望它如何序列化和/或反序列化特定类型的对象。 对于一个IPAddress
,似乎获得让每个人都满意的数据的最简单方法就是简单地将其转换为其字符串表示并返回。 我们可以在转换器中做到这一点。 这是我的写法:
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);
}
}
很简单,就像这些事情一样。 但是,这还不是故事的结局。 如果您需要 带着IPEndPoint
往返 ,那么您也需要一个转换器。 为什么? 由于IPEndPoint
不包含默认构造函数,因此 Json.Net 不知道如何实例化它。 幸运的是,这个转换器也不难写:
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);
}
}
那么,现在我们有了转换器,我们如何使用它们? 这是一个演示的简单示例程序。 它首先创建几个终结点,使用自定义转换器将它们序列化为 JSON,然后立即使用相同的转换器再次将 JSON 反序列化回终结点。
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);
}
}
}
这是输出:
[
{
"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
小提琴:https://dotnetfiddle.net/tK7NKY
关于现有的接受和其他正确答案,需要注意的一件事:
从 .NET Core 3.0 开始return (objectType == typeof(IPAddress));
已损坏。有关更多详细信息,请参阅 Github 中的相关问题。这是在 .NET 中引入修复程序以IPAddress
真正只读之后发生的。详情请见此处。有一个新的ReadOnlyIPAddress
类,它IPAddress
子类并使事情复杂化。
简单的解决方案是将上面的检查替换为
return objectType.IsAssignableTo(typeof(IPAddress));
或者如果您更喜欢 .NET 框架兼容的方式
return typeof(IPAddress).IsAssignableFrom(objectType);
我添加这个作为答案,因为变化并不明显,如果没有涵盖该案例的单元测试套件,很容易进入生产环境。