2013-04-08 3 views
44

я хотел бы сериализации этот код через 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; } 
} 

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

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

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

Thanks

+0

Что вы уже пробовали? Вы даже читали документацию для JSON.NET ?! Я уверен, что сериализация и десериализация - одна из первых вещей, которую эта документация будет охватывать. – Clint

+0

Да Сериализация работает, но я получаю сообщение об ошибке при попытке десериализации: Newtonsoft.Json.JsonSerializationException: Не удалось создать экземпляр типа ITestInterface. Тип - это интерфейс или абстрактный класс и не может быть создан. – user1130329

+2

, то, возможно, вам стоит привести это к вашему вопросу? Вместо того, чтобы спрашивать такие открытые «как я могу»? «Это не работает», вам действительно нужно предоставить всю информацию, какая ошибка? Где это происходит? Что вы пытались исправить до сих пор? Пожалуйста, отредактируйте свой вопрос с этими вещами, чтобы сообщество могло лучше помочь вам, а не помечать ваши вопросы. – Clint

ответ

27

Пыльника полного рабочего примера с тем, что вы хотите сделать:

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(); 
    } 
} 
+0

Это работает просто отлично. Большое спасибо! – user1130329

+0

Вы можете использовать внутри связующего FullyQualifiedName вместо имени, и вам не нужно будет передавать TypeFormat –

+0

Действительно помогло мне в моей проблеме тоже !!! Спасибо alot –

8

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

Вы также можете найти информацию об этой проблеме на этом С.О. вопроса: Casting interfaces for deserialization in JSON.NET

+0

В блоге, приведенном здесь, содержится более подробное объяснение. upvote for erik –

71

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

Я действительно обнаружил, что мне не нужны какие-либо пользовательские связующие, 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 
}); 

Соответствующая документация: Serialization Settings for Json.NET и TypeNameHandling setting

+7

Это действительно работает, но люди должны понимать, что это может повредить размер вашего json большого времени. Для меня он увеличил размер выходного файла более чем на 10X. – 2015-02-16 04:06:15

+2

Обратите внимание, что включение имен конкретного типа в результирующий JSON можно считать утечкой данных реализации и, безусловно, не является чистым, если JSON используется вне вашего собственного кода. Кроме того, если JSON, который вы десериализуете, поступает из внешнего источника, ожидая, что он будет включать ваши имена типов, является необоснованным. –

+1

С помощью этого решения необходимо дезинфицировать входящие типы, чтобы избежать потенциальных угроз безопасности. Подробную информацию см. В разделе [ТипNameHandling осторожность в Newtonsoft Json] (https://stackoverflow.com/q/39565954/3744182). – dbc

13

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

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 }); 
+1

Через несколько лет после первоначального поста, но это современный и эффективный способ сделать это. Цена, которую вы платите, заключается в том, что имена типов выводятся как атрибуты «$ type» для каждого объекта в JSON, но это нормально во многих случаях. – Grubl3r

+0

Это правильное решение, а также для более сложных структур данных. Взял меня буквально на несколько дней, чтобы найти его, хотя ... Вы можете добавить в настройки 'TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Простой' максимально сократить длину вывода – metafa

+0

С помощью этого решения также необходимо дезинфицировать входящие типы, чтобы избежать потенциальных угроз безопасности. Подробную информацию см. В разделе [ТипNameHandling осторожность в Newtonsoft Json] (https://stackoverflow.com/q/39565954/3744182). – dbc

3

Near-дубликат ответа Inrego, но это заслуживает дальнейшего объяснения:

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

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

4

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

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

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

2

Я хотел десериализовать 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; } 
} 
Смежные вопросы