Почему JsonConvert.DeserializeObject не использует указанный JsonConverter? - PullRequest
0 голосов
/ 16 мая 2018

Я написал пользовательский JsonConverter, который я могу присвоить JsonSerializerSettings и использовать с общим переопределением JsonConvert.DeserializeObject, просто отлично:

var settings = new JsonSerializerSettings()
{
    TypeNameHandling = TypeNameHandling.All,
    Converters = new List<JsonConverter>() { new MyConverter() }
};
var x = JsonConvert.DeserializeObject<MyType>(input, settings);

Сериализованный Json был построен также с использованием TypeNameHandling.All, поэтому он содержит информацию о типе в поле $type.

Однако в некоторых сценариях я не знаю, какой тип был сериализован, и хотел бы использовать неуниверсальное переопределение DeserializeObject.Я ожидал, что, если я буду использовать те же настройки и / или конвертер, а Json будет содержать информацию о типе, что движок сможет правильно обрабатывать Json.

Но, похоже, мой пользовательский конвертер используется только длявложенные объекты в Json, а не для самого верхнего уровня - несмотря на $type на каждом уровне.

Моя проблема в том, что без моего пользовательского конвертера мне нужен конструктор по умолчанию для класса.Если я реализую это - только для тестирования - тогда DeserializeObject действительно возвращает правильный тип.Но это не реальный вариант: помимо прочего, пользовательский конвертер разрешает требуемые экземпляры, используя контейнер IOC, а затем заполняет их.

Я что-то упустил или я просто не запрашиваювозможно?

РЕДАКТИРОВАТЬ: Поскольку это было запрошено, ниже приведен пример кода.В этом примере десериализация работала без конструктора по умолчанию, очевидно, что другой вызывался с нулевыми значениями (или default(T)).Но основная проблема все еще существует: ExampleConverter не используется, как я ожидал.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;

namespace UnitTests.Serialization
{
    public class Example
    {
        public Example()
        {
            Console.WriteLine("...Example Default Ctor...");
        }

        public Example(Guid guid)
        {
            Console.WriteLine("...Example Ctor: " + guid.ToString());
        }
        public string ExampleProp { get; set; }
    }

    public class ExampleConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer){throw new NotImplementedException();}

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer){
            Console.WriteLine("...ExampleConverter.ReadJson...");
            var result = new Example(Guid.Empty);

            serializer.Populate(reader, result);

            return result;
        }

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

    [TestClass]
    public class JsonTests
    {
        [TestMethod]
        public void TestDeserializeObject()
        {
            var writeSettings = new JsonSerializerSettings() {TypeNameHandling = TypeNameHandling.All,};
            var readSettings = new JsonSerializerSettings() {TypeNameHandling = TypeNameHandling.All,Converters = new List<JsonConverter>() { new ExampleConverter() }};

            Console.WriteLine("Creating Example...");
            var e1 = new Example(Guid.NewGuid()) { ExampleProp = "some value"};

            Console.WriteLine("\nSerializing e1...");
            var json = Newtonsoft.Json.JsonConvert.SerializeObject(e1, writeSettings);
            Console.WriteLine("e1: " + json);

            Console.WriteLine("\nDeserializing e2 - using DeserializeObject<Example>...");
            var e2 = Newtonsoft.Json.JsonConvert.DeserializeObject<Example>(json, readSettings);
            Console.WriteLine("e2: " +  Newtonsoft.Json.JsonConvert.SerializeObject(e2, writeSettings));

            Console.WriteLine("\nDeserializing e3 - using DeserializeObject...");
            var e3 = Newtonsoft.Json.JsonConvert.DeserializeObject(json, readSettings);
            Console.WriteLine("e3: " + Newtonsoft.Json.JsonConvert.SerializeObject(e2, writeSettings));

        }
    }
}

Вывод:

Creating Example...
...Example Ctor: d860aa00-4493-4ab0-b681-f0af7b123212

Serializing e1...
e1: {"$type":"UnitTests.Serialization.Example, UnitTests","ExampleProp":"some value"}

Deserializing e2 - using DeserializeObject<Example>...
...ExampleConverter.ReadJson...
...Example Ctor: 00000000-0000-0000-0000-000000000000
e2: {"$type":"UnitTests.Serialization.Example, UnitTests","ExampleProp":"some value"}

Deserializing e3 - using DeserializeObject...
...Example Default Ctor...
e3: {"$type":"UnitTests.Serialization.Example, UnitTests","ExampleProp":"some value"}

РЕДАКТИРОВАТЬ: Я также нашел это, но ответ кажется неправильным: Как десериализовать JSON для объектов правильного типа, без необходимости определять тип заранее?

1 Ответ

0 голосов
/ 17 мая 2018

Для моего конкретного варианта использования сработало следующее:

public object DeserializeFromTypedString(string input, JsonSerializerSettings settings)
{
    var r = new Regex(@"^\{\s*""\$type"":\s*""([^""]+)""");

    var m = r.Match(input);
    if (m.Success)
    {
        var t = Type.GetType(m.Groups[1].Value);
        return Newtonsoft.Json.JsonConvert.DeserializeObject(input, t, settings);
    }
    else
    {
        throw new Exception("$type not found!");
    }
}

Идея состоит в том, чтобы вручную проанализировать строку для определения $type, определить тип и использовать его для вызова соответствующего DeserializeObject overload.

Это было достаточно для меня, потому что в моем конкретном случае использования я всегда получал JSON в виде строки (а не, например, Stream или JToken), представляющей один корневой объект (а не массив),Решение может быть соответствующим образом адаптировано, , но я все еще надеюсь на лучшее / более чистое решение .

Если я добавлю метод и обменяю соответствующие строки в моем примере сверху ...

Console.WriteLine("\nDeserializing e3 - using DeserializeFromTypeString...");
var e3 = DeserializeFromTypedString(json, readSettings);

... вывод (правильно):

Creating Example...
...Example Ctor: b5af2c3f-1c03-49d8-85c6-f3ff60c9f711

Serializing e1...
e1: {"$type":"UnitTests.Serialization.Example, UnitTests","Guid":"b5af2c3f-1c03-49d8-85c6-f3ff60c9f711","ExampleProp":"some value"}

Deserializing e2 - using DeserializeObject<Example>...
...ExampleConverter.ReadJson...
...Example Ctor: 00000000-0000-0000-0000-000000000000
e2: {"$type":"UnitTests.Serialization.Example, UnitTests","Guid":"00000000-0000-0000-0000-000000000000","ExampleProp":"some value"}

Deserializing e3 - using DeserializeFromTypedString...
...ExampleConverter.ReadJson...
...Example Ctor: 00000000-0000-0000-0000-000000000000
e3: {"$type":"UnitTests.Serialization.Example, UnitTests","Guid":"00000000-0000-0000-0000-000000000000","ExampleProp":"some value"}

Обратите внимание на вывод ExampleConverter.ReadJson и Example Ctor в обоих случаях десериализации, что показывает, что мой пользовательский конвертер действительно используется.(Я также успешно проверил это с вложенными объектами в моем фактическом коде.)

...