web-dev-qa-db-ja.com

Json.Net-「System.Net.IPAddress」の「ScopeId」から値を取得中にエラーが発生しました

Json.NetでIPEndpointオブジェクトをシリアル化しようとすると、次のエラーが発生します。

「System.Net.IPAddress」の「ScopeId」から値を取得中にエラーが発生しました。

エラーの原因は、エンドポイントでIPAddressオブジェクトのIPV4プロパティのみを使用していることです。 JsonパーサーがIPv6部分を解析しようとすると、ScopeIDプロパティにアクセスしてソケット例外「試行された操作は、参照されているオブジェクトのタイプではサポートされていません」をスローします(nullはMicrosoftで十分です)。

すべてを引き裂いてアドレス情報を文字列としてコード化する以外に、これに対する回避策があるのではないかと思っていましたか?ある時点で、IPV6をサポートしたいと思っています。 IPAddressファミリがInternetworkIPV6ではなくInternetworkに設定されている場合、Json.NETでエラーを無視したり、ScopeIDのシリアル化を試みないだけでできることはありますか?

おかげで、

ディンズデール

33
Dinsdale

ご覧のように、IPAddressクラスはシリアル化にあまり適していません。 IPv4アドレスのSocketExceptionフィールドにアクセスしようとするとScopeIDがスローされるだけでなく、Addressフィールドに直接アクセスしようとするとスローされますIPv6アドレス。

例外を回避するには、カスタム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

60
Brian Rogers