Использование преобразователей Json.NET для десериализации свойств - PullRequest
83 голосов
/ 12 февраля 2010

У меня есть определение класса, которое содержит свойство, которое возвращает интерфейс.

public class Foo
{ 
    public int Number { get; set; }

    public ISomething Thing { get; set; }
}

Попытка сериализации класса Foo с использованием Json.NET выдает мне сообщение об ошибке типа «Не удалось создать экземпляр типа« ISomething ». ISomething может быть интерфейсом или абстрактным классом».

Существует ли атрибут или конвертер Json.NET, который позволил бы мне указать конкретный класс Something, который будет использоваться при десериализации?

Ответы [ 9 ]

86 голосов
/ 06 марта 2010

Одна из вещей, которую вы можете сделать с Json.NET :

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;

JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

Флаг TypeNameHandling добавит свойство $type в JSON, что позволит Json.NET узнать, в какой конкретный тип требуется десериализовать объект. Это позволяет десериализовать объект, все еще выполняя интерфейс или абстрактный базовый класс.

Недостатком, однако, является то, что это очень специфично для Json.NET. $type будет полностью квалифицированным типом, поэтому, если вы сериализуете его с помощью информации о типе, десериализатор должен уметь это понимать.

Документация: Настройки сериализации с помощью Json.NET

49 голосов
/ 04 ноября 2010

Этого можно добиться с помощью класса JsonConverter. Предположим, у вас есть класс со свойством интерфейса;

public class Organisation {
  public string Name { get; set; }

  [JsonConverter(typeof(TycoonConverter))]
  public IPerson Owner { get; set; }
}

public interface IPerson {
  string Name { get; set; }
}

public class Tycoon : IPerson {
  public string Name { get; set; }
}

Ваш JsonConverter отвечает за сериализацию и десериализацию базового свойства;

public class TycoonConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return (objectType == typeof(IPerson));
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    return serializer.Deserialize<Tycoon>(reader);
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

Когда вы работаете с Организацией, десериализованной через Json.Net, базовый IPerson для свойства Owner будет иметь тип Tycoon.

38 голосов
/ 10 июня 2011

Вместо передачи настроенного объекта JsonSerializerSettings в JsonConvert.SerializeObject () с параметром TypeNameHandling.Objects, как упоминалось ранее, вы можете просто пометить это конкретное свойство интерфейса с атрибутом, чтобы сгенерированный JSON не был раздутым с помощью $ type "свойства КАЖДОГО объекта:

public class Foo
{
    public int Number { get; set; }

    // Add "$type" property containing type info of concrete class.
    [JsonProperty( TypeNameHandling = TypeNameHandling.Objects )]
    public ISomething { get; set; }
}
21 голосов
/ 01 марта 2013

В самой последней версии стороннего конвертера Newtonsoft Json вы можете установить конструктор с конкретным типом, относящимся к свойству interfaced.

public class Foo
{ 
    public int Number { get; private set; }

    public ISomething IsSomething { get; private set; }

    public Foo(int number, Something concreteType)
    {
        Number = number;
        IsSomething = concreteType;
    }
}

Пока что-то реализует что-то, это должно работать. Также не помещайте пустой конструктор по умолчанию в случае, если конвертер JSon пытается использовать это, вы должны принудительно использовать конструктор, содержащий конкретный тип.

PS. это также позволяет вам сделать ваши сеттеры приватными.

16 голосов
/ 28 февраля 2012

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

public class JsonKnownTypeConverter : JsonConverter
{
    public IEnumerable<Type> KnownTypes { get; set; }

    public JsonKnownTypeConverter(IEnumerable<Type> knownTypes)
    {
        KnownTypes = knownTypes;
    }

    protected object Create(Type objectType, JObject jObject)
    {
        if (jObject["$type"] != null)
        {
            string typeName = jObject["$type"].ToString();
            return Activator.CreateInstance(KnownTypes.First(x =>typeName.Contains("."+x.Name+",")));
        }

        throw new InvalidOperationException("No supported type");
    }

    public override bool CanConvert(Type objectType)
    {
        if (KnownTypes == null)
            return false;

        return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);
        // Create target object based on JObject
        var target = Create(objectType, jObject);
        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);
        return target;
    }

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

Я определил два метода расширения для десериализации и сериализации:

public static class AltiJsonSerializer
{
    public static T DeserializeJson<T>(this string jsonString, IEnumerable<Type> knownTypes = null)
    {
        if (string.IsNullOrEmpty(jsonString))
            return default(T);

        return JsonConvert.DeserializeObject<T>(jsonString,
                new JsonSerializerSettings
                {
                    TypeNameHandling = TypeNameHandling.Auto, 
                    Converters = new List<JsonConverter>
                        (
                            new JsonConverter[]
                            {
                                new JsonKnownTypeConverter(knownTypes)
                            }
                        )
                }
            );
    }

    public static string SerializeJson(this object objectToSerialize)
    {
        return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented,
        new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
    }
}

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

2 голосов
/ 15 июня 2017

Я просто хотел завершить пример, который @Daniel T. показал нам выше:

Если вы используете этот код для сериализации вашего объекта:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

Код для десериализации JSON должен выглядеть следующим образом:

* +1007 *

Вот как json соответствует при использовании флага TypeNameHandling: enter image description here

2 голосов
/ 30 августа 2012

Обычно я всегда использовал решение с TypeNameHandling в соответствии с предложением DanielT, но в тех случаях, когда здесь я не имел контроля над входящим JSON (и поэтому не могу гарантировать, что оно включает свойство $type), я написал Пользовательский конвертер, который просто позволяет явно указать конкретный тип:

public class Model
{
    [JsonConverter(typeof(ConcreteTypeConverter<Something>))]
    public ISomething TheThing { get; set; }
}

Это просто использует реализацию сериализатора по умолчанию из Json.Net при явном указании конкретного типа.

Исходный код и обзор доступны в этом сообщении в блоге .

0 голосов
/ 15 февраля 2010

Я удивлялся этой же вещи, но боюсь, что это невозможно.

Давайте посмотрим на это так. Вы передаете JSon.net строку данных и тип для десериализации. Что делать JSON.net, когда он попадает в этот ISomething? Он не может создать новый тип ISomething, потому что ISomething не является объектом. Он также не может создать объект, который реализует ISomething, поскольку он не имеет ни малейшего понятия, какой из множества объектов, которые могут наследовать ISomething, следует использовать. Интерфейсы - это то, что можно автоматически сериализовать, но не десериализовать автоматически.

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

0 голосов
/ 13 февраля 2010

Вот ссылка на статью, написанную ScottGu

Исходя из этого, я написал некоторый код, который, я думаю, может быть полезен

public interface IEducationalInstitute
{
    string Name
    {
        get; set;
    }

}

public class School : IEducationalInstitute
{
    private string name;
    #region IEducationalInstitute Members

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    #endregion
}

public class Student 
{
    public IEducationalInstitute LocalSchool { get; set; }

    public int ID { get; set; }
}

public static class JSONHelper
{
    public static string ToJSON(this object obj)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        return serializer.Serialize(obj);
    }
    public  static string ToJSON(this object obj, int depth)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        serializer.RecursionLimit = depth;
        return serializer.Serialize(obj);
    }
}

А вот как бы вы назвали это

School myFavSchool = new School() { Name = "JFK High School" };
Student sam = new Student()
{
    ID = 1,
    LocalSchool = myFavSchool
};
string jSONstring = sam.ToJSON();

Console.WriteLine(jSONstring);
//Result {"LocalSchool":{"Name":"JFK High School"},"ID":1}

Если я правильно понимаю, я не думаю, что вам нужно указывать конкретный класс, который реализует интерфейс для сериализации JSON.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...