Почему System.Version в строке JSON не десериализуется правильно?

Контекст: мне нужно передать объект, содержащий большое количество свойств/полей (для слоя UI из слоя среднего уровня). Среди этого списка свойств один имеет тип Version, который не получает десериализован правильно из формата строки JSON. У меня есть выбранный формат JSON по XML, поскольку сериализация JSON в строку вернет результат короткой строки.

Проблема: System.Version не десериализуется правильно. Я пробовал две разные библиотеки .NET. Ниже приведены фрагменты кода для каждого:

Фрагмент кода 1 с помощью Библиотека ServiceStack.NET:

        var version = new Version(1, 2, 3, 0);
        string reportJSON = JsonSerializer.SerializeToString<Version>(version);
        //{"Major":1,"Minor":2,"Build":3,"Revision":0,"MajorRevision":0,"MinorRevision":0}


        Version report2 = JsonSerializer.DeserializeFromString<Version>(reportJSON);
        string reportJSON2 = JsonSerializer.SerializeToString<Version>(report2);
        //{"Major":0,"Minor":0,"Build":-1,"Revision":-1,"MajorRevision":-1,"MinorRevision":-1}

Фрагмент кода 2 с использованием Newtonsoft.NET library, но с тем же результатом:

        var version = new Version(1, 2, 3, 0);
        string reportJSON = JsonConvert.SerializeObject(version);
        //{"Major":1,"Minor":2,"Build":3,"Revision":0,"MajorRevision":0,"MinorRevision":0}


        Version report2 = JsonConvert.DeserializeObject<Version>(reportJSON);
        string reportJSON2 = JsonConvert.SerializeObject(report2);
        //{"Major":0,"Minor":0,"Build":-1,"Revision":-1,"MajorRevision":-1,"MinorRevision":-1}

Как это исправить? Или Какая другая библиотека JSON.NET может работать правильно?

Ответ 1

Библиотека Newtonsoft.Json предоставляет набор общих преобразователей в пространстве имен Newtonsoft.Json.Converters, включая VersionConverter, который вы можете использовать для сериализации и десериализации System.Version.

Обратите внимание, что вы должны использовать VersionConverter как для сериализации, так и для десериализации, хотя.
Это связано с тем, что стандартная сериализация генерирует, например: {"Major":1,"Minor":2,"Build":3,"Revision":0,"MajorRevision":0,"MinorRevision":0}, в то время как VersionConverter десериализация ожидает простую строку, как в "1.2.3".

Таким образом, использование будет:

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;  

string s = JsonConvert.SerializeObject(version, new VersionConverter());
Version v = JsonConvert.DeserializeObject<Version>(s, new VersionConverter());

Я не уверен, что первая версия Newtonsoft.Json, которая включает этот конвертер. У меня есть это, и он 5.0.6.

Ответ 2

В свойствах класса Version нет setter. Они просто возвращают значение своих соответствующих частных полей. Поэтому десериализатор не может изменить свои значения.

Но с Json.NET вы можете написать собственный класс конвертера, который обрабатывает десериализацию класса Version.

Остерегайтесь: этот код не был протестирован очень хорошо...

public class VersionConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // default serialization
        serializer.Serialize(writer, value);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // create a new Version instance and pass the properties to the constructor
        // (you may also use dynamics if you like)
        var dict = serializer.Deserialize<Dictionary<string, int>>(reader);
        return new Version(dict["Major"], dict["Minor"], dict["Build"], dict["Revision"]);
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Version);
    }
}

Затем вы должны указать, что хотите использовать конвертер:

var v = new Version(1, 2, 3, 4);
string json = JsonConvert.SerializeObject(v);

var v2 = JsonConvert.DeserializeObject<Version>(json, new VersionConverter());

Json.NET решает, использовать ли один из указанных вами конвертеров. Поэтому вы всегда можете указать преобразователь, как показано ниже. Json.NET будет использовать один из ваших конвертеров, если они соответствуют типу внутри SomeClass.

var result = JsonConvert.DeserializeObject<SomeClass>(json, new VersionConverter());