Использование конвертеров Json.NET для десериализации свойств

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

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

    public ISomething Thing { get; set; }
}

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

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


person dthrasher    schedule 12.02.2010    source источник
comment
Я считаю, что вам нужно указать имя свойства, которое получает / устанавливает ISomething   -  person ram    schedule 13.02.2010
comment
У меня есть. Я использую сокращение для автоматически реализуемых свойств, представленное в C # 3.5. msdn.microsoft.com/en-us/library/bb384054.aspx   -  person dthrasher    schedule 14.02.2010
comment
Разве это не что-то типа. Я думаю, что ram прав, вам все еще нужно имя свойства. Я знаю, что это не связано с вашей проблемой, но ваш комментарий выше заставил меня подумать, что мне не хватает какой-то новой функции в .NET, которая позволяла вам указывать свойство без имени.   -  person Mr Moose    schedule 23.05.2012


Ответы (9)


Одна из вещей, которые вы можете сделать с помощью 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

person Daniel T.    schedule 06.03.2010
comment
Для Newtonsoft.Json он работает аналогично, но свойство $ type - person Jaap; 08.04.2012
comment
Обратите внимание на возможные проблемы с безопасностью при использовании TypeNameHandling. Дополнительные сведения см. В осторожности при использовании TypeNameHandling в Newtonsoft Json. - person dbc; 15.12.2017

Вы можете добиться этого с помощью класса 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.

person MrMDavidson    schedule 04.11.2010
comment
Будет ли тег [JsonConverter (typeof (TycoonConverter))] работать, если он есть в списке интерфейса? - person Zwik; 02.04.2014

Вместо передачи настроенного объекта 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; }
}
person Erhhung    schedule 10.06.2011
comment
Для коллекций интерфейсов или абстрактных классов свойство - ItemTypeNameHandling. например : [JsonProperty (ItemTypeNameHandling = TypeNameHandling.Auto)] - person Anthony F; 14.02.2018

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

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;
    }
}

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

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

person SamuelDavis    schedule 28.02.2013
comment
Это нужно кричать с крыш! Конечно, это добавляет ограничения на конкретную реализацию, но это намного проще, чем другие подходы для тех ситуаций, в которых его можно использовать. - person Mark Meuer; 09.08.2013
comment
Что, если у нас есть более одного конструктора с несколькими конкретными типами, он все равно будет знать? - person Teoman shipahi; 19.11.2014
comment
Этот ответ настолько элегантен по сравнению со всей запутанной чушью, которую вам пришлось бы делать в противном случае. Это должен быть принятый ответ. Однако в моем случае одно предостережение заключалось в том, что мне пришлось добавить [JsonConstructor] перед конструктором, чтобы он работал ... Я подозреваю, что использование этого только в ОДНОМ из ваших конкретных конструкторов решит вашу (4-летнюю) проблему. @Teomanshipahi - person nacitar sevaht; 11.01.2018
comment
@nacitarsevaht Я могу вернуться и исправить свою проблему сейчас :) в любом случае я даже не помню, что это было, но когда я снова смотрю, это хорошее решение для определенных случаев. - person Teoman shipahi; 11.01.2018
comment
мы тоже используем это, но я предпочитаю convert в большинстве случаев, потому что связывание конкретного типа с конструктором в первую очередь лишает смысла использование интерфейса для свойства! - person gabe; 24.09.2019

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

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

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

person Bruno Altinet    schedule 28.02.2012
comment
Этот JsonConverter великолепен, я использовал его, но столкнулся с несколькими проблемами, которые я решил таким образом: - Использование JsonSerializer.CreateDefault () вместо заполнения, потому что у моего объекта была более глубокая иерархия. - Использование отражения для получения конструктора и создания его экземпляра в методе Create () - person Aurel; 22.05.2017

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

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

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

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

person Steve Greatrex    schedule 30.08.2012
comment
Это отличное решение. Ваше здоровье. - person JohnMetta; 14.02.2018

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

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

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

Код десериализации json должен выглядеть так:

var settings = new JsonSerializerSettings(); 
settings.TypeNameHandling = TypeNameHandling.Objects;
var entity = JsonConvert.DeserializeObject<EntityType>(json, settings);

Вот как согласовывается json при использовании флага TypeNameHandling:  введите описание изображения здесь

person Luis Armando    schedule 15.06.2017

Я задавался вопросом о том же самом, но боюсь, что это невозможно.

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

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

person Timothy Baldridge    schedule 15.02.2010
comment
Я понимаю, что из коробки ничего не выйдет. Но мне было интересно, есть ли какой-нибудь атрибут вроде [JsonProperty (typeof (SomethingBase))], который я мог бы использовать для предоставления конкретного класса. - person dthrasher; 16.02.2010
comment
Так почему бы не использовать SomethingBase вместо ISomething в приведенном выше коде? Можно утверждать, что мы тоже смотрим на это неправильно, поскольку интерфейсы не должны использоваться при сериализации, поскольку они просто определяют интерфейс связи с данным классом. Технически сериализация интерфейса - это ерунда, как и сериализация абстрактного класса. Так что, хотя это можно сделать, я утверждаю, что этого делать не следует. - person Timothy Baldridge; 16.02.2010
comment
Вы смотрели на какие-либо классы в пространстве имен Newtonsoft.Json.Serialization? особенно класс JsonObjectContract? - person johnny; 17.02.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.

person ram    schedule 12.02.2010
comment
В вашем примере используется JavaScriptSerializer, класс в .NET Framework. Я использую Json.NET в качестве сериализатора. codeplex.com/Json - person dthrasher; 14.02.2010
comment
Не относится к исходному вопросу, там явно упоминался Json.NET. - person Oliver; 06.12.2012