Подтвердить что ты не робот

JSON.NET - как десериализовать сборку интерфейсных экземпляров?

Я хотел бы сериализовать этот код через json.net:

public interface ITestInterface
{
    string Guid {get;set;}
}

public class TestClassThatImplementsTestInterface1
{
    public string Guid { get;set; }
}

public class TestClassThatImplementsTestInterface2
{
    public string Guid { get;set; }
}


public class ClassToSerializeViaJson
{
    public ClassToSerializeViaJson()
    {             
         this.CollectionToSerialize = new List<ITestInterface>();
         this.CollectionToSerialize.add( new TestClassThatImplementsTestInterface2() );
         this.CollectionToSerialize.add( new TestClassThatImplementsTestInterface2() );
    }
    List<ITestInterface> CollectionToSerialize { get;set; }
}

Я хочу сериализовать /deserialize ClassToSerializeViaJson с json.net. Сериализация работает, но десериализация дает мне эту ошибку:

Newtonsoft.Json.JsonSerializationException: Не удалось создать экземпляр типа ITestInterface. Тип - это интерфейс или абстрактный класс и не может быть создан.

Итак, как я могу десериализовать коллекцию List<ITestInterface>?

4b9b3361

Ответ 1

Ниже приведен полный рабочий пример с тем, что вы хотите сделать:

public interface ITestInterface
{
    string Guid { get; set; }
}

public class TestClassThatImplementsTestInterface1 : ITestInterface
{
    public string Guid { get; set; }
    public string Something1 { get; set; }
}

public class TestClassThatImplementsTestInterface2 : ITestInterface
{
    public string Guid { get; set; }
    public string Something2 { get; set; }
}

public class ClassToSerializeViaJson
{
    public ClassToSerializeViaJson()
    {
        this.CollectionToSerialize = new List<ITestInterface>();
    }
    public List<ITestInterface> CollectionToSerialize { get; set; }
}

public class TypeNameSerializationBinder : SerializationBinder
{
    public string TypeFormat { get; private set; }

    public TypeNameSerializationBinder(string typeFormat)
    {
        TypeFormat = typeFormat;
    }

    public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        assemblyName = null;
        typeName = serializedType.Name;
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
        var resolvedTypeName = string.Format(TypeFormat, typeName);
        return Type.GetType(resolvedTypeName, true);
    }
}

class Program
{
    static void Main()
    {
        var binder = new TypeNameSerializationBinder("ConsoleApplication.{0}, ConsoleApplication");
        var toserialize = new ClassToSerializeViaJson();

        toserialize.CollectionToSerialize.Add(
            new TestClassThatImplementsTestInterface1()
            {
                Guid = Guid.NewGuid().ToString(), Something1 = "Some1"
            });
        toserialize.CollectionToSerialize.Add(
            new TestClassThatImplementsTestInterface2()
            {
                Guid = Guid.NewGuid().ToString(), Something2 = "Some2"
            });

        string json = JsonConvert.SerializeObject(toserialize, Formatting.Indented, 
            new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                Binder = binder
            });
        var obj = JsonConvert.DeserializeObject<ClassToSerializeViaJson>(json, 
            new JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.Auto,
                Binder = binder 
            });

        Console.ReadLine();
    }
}

Ответ 2

Я нашел этот вопрос, пытаясь сделать это сам. После того, как я реализовал ответ Петра Стаппа (Гарата), я был поражен тем, насколько простым это показалось. Если я просто реализовывал метод, которому уже передавался точный тип (в виде строки), который я хотел создать, почему библиотека не связывала его автоматически?

На самом деле я обнаружил, что мне не нужно никаких пользовательских папок, Json.Net мог делать именно то, что мне нужно, при условии, что я сказал, что это то, что я делал.

При сериализации:

string serializedJson = JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
});

При десериализации:

var deserializedObject = JsonConvert.DeserializeObject<ClassToSerializeViaJson>(serializedJson, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.Objects
});

Соответствующая документация: параметры сериализации для Json.NET и TypeNameHandling.

Ответ 3

Я также был удивлен простотой в Garath's, и также пришел к выводу, что библиотека Json может делать это автоматически. Но я также подумал, что это даже проще, чем ответ Бена Дженкинсона (хотя я вижу, что он был изменен самим разработчиком библиотеки json). Из моих тестов все, что вам нужно сделать, это установить TypeNameHandling в Auto, например:

var objectToSerialize = new List<IFoo>();
// TODO: Add objects to list
var jsonString = JsonConvert.SerializeObject(objectToSerialize,
       new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });
var deserializedObject = JsonConvert.DeserializeObject<List<IFoo>>(jsonString, 
       new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto });

Из документации перечисления TypeNameHandling

Авто: включите имя типа .NET, если тип сериализуемого объекта не совпадает с его объявленным типом. Обратите внимание, что это не включает корневой сериализованный объект по умолчанию.

Ответ 4

Используя настройки по умолчанию, вы не можете. JSON.NET не знает, как десериализовать массив. Однако вы можете указать, какой тип конвертера использовать для вашего типа интерфейса. Чтобы узнать, как это сделать, см. Эту страницу: http://blog.greatrexpectations.com/2012/08/30/deserializing-interface-properties-using-json-net/

Вы также можете найти информацию об этой проблеме на этом SO-вопросе: Кастинг-интерфейсы для десериализации в JSON.NET

Ответ 5

Это старый вопрос, но я подумал, что добавлю более подробный ответ (в виде статьи, которую я написал): http://skrift.io/articles/archive/bulletproof-interface-deserialization-in-jsonnet/

TL;DR: Вместо того, чтобы настраивать Json.NET для встраивания имен типов в сериализованный JSON, вы можете использовать JSON-конвертер, чтобы выяснить, какой класс следует десериализовать для использования любой настраиваемой логики.

Это имеет то преимущество, что вы можете реорганизовать свои типы, не беспокоясь о нарушении десериализации.

Ответ 6

Почти дубликат ответа Инрего, но он заслуживает дальнейшего объяснения:

Если вы используете TypeNameHandling.Auto, тогда он включает только имя типа/сборки, когда требуется (например, интерфейсы и базовые/производные классы). Таким образом, ваш JSON чище, меньше, более конкретный.

Что не является одной из основных точек продажи по XML/SOAP?

Ответ 7

Я хотел десериализовать JSON, который не был сериализован моим приложением, поэтому мне нужно было указать конкретную реализацию вручную. Я расширил ответ Николаса.

Допустим, что

public class Person
{
    public ILocation Location { get;set; }
}

и конкретный пример

public class Location: ILocation
{
    public string Address1 { get; set; }
    // etc
}

Добавить в этот класс

public class ConfigConverter<I, T> : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(I);
    }
    public override void WriteJson(JsonWriter writer,
        object value, JsonSerializer serializer)
    {
        throw new InvalidOperationException("Use default serialization.");
    }

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var deserialized = (T)Activator.CreateInstance(typeof(T));
        serializer.Populate(jsonObject.CreateReader(), deserialized);
        return deserialized;
    }
}

Затем определите свои интерфейсы с атрибутом JsonConverter

public class Person
{
    [JsonConverter(typeof(ConfigConverter<ILocation, Location>))]
    public ILocation Location { get;set; }
}

Ответ 8

Это можно сделать с помощью атрибутов JSON.NET и JsonSubTypes:

[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Test1), "Something1")]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Test2), "Something2")]
public interface ITestInterface
{
    string Guid { get; set; }
}

public class Test1 : ITestInterface
{
    public string Guid { get; set; }
    public string Something1 { get; set; }
}

public class Test2 : ITestInterface
{
    public string Guid { get; set; }
    public string Something2 { get; set; }
}

и просто:

var fromCode = new List<ITestInterface>();
// TODO: Add objects to list
var json = JsonConvert.SerializeObject(fromCode);
var fromJson = JsonConvert.DeserializeObject<List<ITestInterface>>(json);

Ответ 9

Избегайте использования TypeNameHandling.Auto, когда это возможно, особенно с контролируемыми пользователем значениями.

Вам нужно будет написать собственный десериализатор для типа коллекции.

Вместо повторения других, которые уже опубликовали код конвертера (в частности, Николас Вестби, чей пост в блоге был очень полезен и связан выше), я включил соответствующие изменения для десериализации коллекции интерфейсов (у меня было свойство интерфейса enum, чтобы отличать разработчиков ):

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        Collection<T> result = new Collection<T>();
        var array = JArray.Load(reader);
        foreach (JObject jsonObject in array)
        { 
            var rule = default(T);
            var value = jsonObject.Value<string>("MyDistinguisher");
            MyEnum distinguisher;
            Enum.TryParse(value, out distinguisher);
            switch (distinguisher)
            {
                case MyEnum.Value1:
                    rule = serializer.Deserialize<Type1>(jsonObject.CreateReader());
                    break;
                case MyEnum.Value2:
                    rule = serializer.Deserialize<Type2>(jsonObject.CreateReader());
                    break;
                default:
                    rule = serializer.Deserialize<Type3>(jsonObject.CreateReader());
                    break;
            }
            result.Add(rule);
        }
        return result;
    }

Надеюсь, это полезно для следующего человека, ищущего десериализатор интерфейса.