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

Класс NewtonSoft.Json Serialize и Deserialize с свойством типа IEnumerable <ISomeInterface>

Я пытаюсь переместить некоторый код, чтобы использовать ASP.NET MVC Web API, генерируемые Json-данными вместо SOAP Xml.

У меня возникла проблема с сериализацией и десериализацией свойств типа:

IEnumerable<ISomeInterface>.

Вот простой пример:

public interface ISample{
  int SampleId { get; set; }
}
public class Sample : ISample{
  public int SampleId { get; set; }
}
public class SampleGroup{
  public int GroupId { get; set; }
  public IEnumerable<ISample> Samples { get; set; }
 }
}

Я могу легко сериализовать экземпляры SampleGroup с помощью:

var sz = JsonConvert.SerializeObject( sampleGroupInstance );

Однако соответствующий десериализация не выполняется:

JsonConvert.DeserializeObject<SampleGroup>( sz );

с этим сообщением об исключении:

"Не удалось создать экземпляр типа JsonSerializationExample.ISample. Тип - это интерфейс или абстрактный класс и не может быть создан."

Если я получаю JsonConverter, я могу украсить свое свойство следующим образом:

[JsonConverter( typeof (SamplesJsonConverter) )]
public IEnumerable<ISample> Samples { get; set; }

Вот JsonConverter:

public class SamplesJsonConverter : JsonConverter{
  public override bool CanConvert( Type objectType ){
    return ( objectType == typeof (IEnumerable<ISample>) );
  }

  public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ){
    var jA = JArray.Load( reader );
    return jA.Select( jl => serializer.Deserialize<Sample>( new JTokenReader( jl ) ) ).Cast<ISample>( ).ToList( );
  }

  public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer ){
    ... What works here?
  }
}

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

Может ли кто-нибудь помочь?

Является ли это "правильным" способом решения проблемы в первую очередь?

4b9b3361

Ответ 1

Вам не нужно использовать JsonConverterAttribute, сохранить свою модель чистой, а также использовать CustomCreationConverter, код проще:

public class SampleConverter : CustomCreationConverter<ISample>
{
    public override ISample Create(Type objectType)
    {
        return new Sample();
    }
}

Тогда:

var sz = JsonConvert.SerializeObject( sampleGroupInstance );
JsonConvert.DeserializeObject<SampleGroup>( sz, new SampleConverter());

Документация: Deserialize with CustomCreationConverter

Ответ 2

Это довольно простая и бесплатная поддержка json.net, вам просто нужно использовать следующие JsonSettings при сериализации и десериализации:

JsonConvert.SerializeObject(graph,Formatting.None, new JsonSerializerSettings()
{
    TypeNameHandling =TypeNameHandling.Objects,
    TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
});

и для Deserialzing используйте приведенный ниже код:

JsonConvert.DeserializeObject(Encoding.UTF8.GetString(bData),type,
    new JsonSerializerSettings(){TypeNameHandling = TypeNameHandling.Objects}
);

Просто обратите внимание на инициализатор объектов JsonSerializerSettings, который важен для вас.

Ответ 3

Я решил эту проблему, используя специальный параметр для JsonSerializerSettings, который называется TypeNameHandling.All

Параметр TypeNameHandling включает информацию о типе при сериализации JSON и информации типа считывания, чтобы создавать типы создания при десериализации JSON

Сериализация:

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var text = JsonConvert.SerializeObject(configuration, settings);

Десериализация:

var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
var configuration = JsonConvert.DeserializeObject<YourClass>(json, settings);

Класс YourClass может иметь любые типы базового типа, и он будет правильно сериализован.

Ответ 4

Отличное решение, спасибо! Я взял вопрос AndyDBell и ответ Cuong Le, чтобы построить пример с двумя вариантами интерфейса:

public interface ISample
{
    int SampleId { get; set; }
}

public class Sample1 : ISample
{
    public int SampleId { get; set; }
    public Sample1() { }
}


public class Sample2 : ISample
{
    public int SampleId { get; set; }
    public String SampleName { get; set; }
    public Sample2() { }
}

public class SampleGroup
{
    public int GroupId { get; set; }
    public IEnumerable<ISample> Samples { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        //Sample1 instance
        var sz = "{\"GroupId\":1,\"Samples\":[{\"SampleId\":1},{\"SampleId\":2}]}";
        var j = JsonConvert.DeserializeObject<SampleGroup>(sz, new SampleConverter<Sample1>());
        foreach (var item in j.Samples)
        {
            Console.WriteLine("id:{0}", item.SampleId);
        }
        //Sample2 instance
        var sz2 = "{\"GroupId\":1,\"Samples\":[{\"SampleId\":1, \"SampleName\":\"Test1\"},{\"SampleId\":2, \"SampleName\":\"Test2\"}]}";
        var j2 = JsonConvert.DeserializeObject<SampleGroup>(sz2, new SampleConverter<Sample2>());
        //Print to show that the unboxing to Sample2 preserved the SampleName values
        foreach (var item in j2.Samples)
        {
            Console.WriteLine("id:{0} name:{1}", item.SampleId, (item as Sample2).SampleName);
        }
        Console.ReadKey();
    }
}

И общая версия для SampleConverter:

public class SampleConverter<T> : CustomCreationConverter<ISample> where T: new ()
{
    public override ISample Create(Type objectType)
    {
        return ((ISample)new T());
    }
}

Ответ 5

В моих проектах эта часть кода всегда работала как сериализатор по умолчанию, который сериализует указанное значение, как если бы не было специального конвертера:

serializer.Serialize(writer, value);

Ответ 6

Я получил это для работы:

явное преобразование

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
                                    JsonSerializer serializer)
    {
        var jsonObj = serializer.Deserialize<List<SomeObject>>(reader);
        var conversion = jsonObj.ConvertAll((x) => x as ISomeObject);

        return conversion;
    }

Ответ 7

После этого:

public interface ITerm
{
    string Name { get; }
}

public class Value : ITerm...

public class Variable : ITerm...

public class Query
{
   public IList<ITerm> Terms { get; }
...
}

Мне удалось преобразовать трюк, реализующий это:

public class TermConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var field = value.GetType().Name;
        writer.WriteStartObject();
        writer.WritePropertyName(field);
        writer.WriteValue((value as ITerm)?.Name);
        writer.WriteEndObject();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var properties = jsonObject.Properties().ToList();
        var value = (string) properties[0].Value;
        return properties[0].Name.Equals("Value") ? (ITerm) new Value(value) : new Variable(value);
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof (ITerm) == objectType || typeof (Value) == objectType || typeof (Variable) == objectType;
    }
}

Это позволяет мне сериализовать и десериализовать в JSON, например:

string JsonQuery = "{\"Terms\":[{\"Value\":\"This is \"},{\"Variable\":\"X\"},{\"Value\":\"!\"}]}";
...
var query = new Query(new Value("This is "), new Variable("X"), new Value("!"));
var serializeObject = JsonConvert.SerializeObject(query, new TermConverter());
Assert.AreEqual(JsonQuery, serializeObject);
...
var queryDeserialized = JsonConvert.DeserializeObject<Query>(JsonQuery, new TermConverter());