program story

Json.NET 변환기를 사용하여 속성 역 직렬화

inputbox 2020. 9. 18. 08:09
반응형

Json.NET 변환기를 사용하여 속성 역 직렬화


인터페이스를 반환하는 속성을 포함하는 클래스 정의가 있습니다.

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

    public ISomething Thing { get; set; }
}

Json.NET을 사용하여 Foo 클래스를 직렬화하려고하면 " 'ISomething'유형의 인스턴스를 만들 수 없습니다. ISomething은 인터페이스 또는 추상 클래스 일 수 있습니다."와 같은 오류 메시지가 나타납니다.

Somethingdeserialization 중에 사용할 구체적인 클래스를 지정할 수있는 Json.NET 특성 또는 변환기가 있습니까?


Json.NET으로 할 수있는 작업 중 하나는 다음과 같습니다 .

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

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

TypeNameHandling플래그는 $typeJSON에 속성을 추가하여 Json.NET이 개체를 역 직렬화해야하는 구체적인 유형을 알 수 있도록합니다. 이를 통해 인터페이스 또는 추상 기본 클래스를 수행하면서 객체를 역 직렬화 할 수 있습니다.

그러나 단점은 이것이 매우 Json.NET 전용이라는 것입니다. $type정규화 된 유형이므로 유형 정보로 직렬화하는 경우 deserializer도이를 이해할 수 있어야합니다.

문서 : Json.NET을 사용한 직렬화 설정


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을 통해 역 직렬화 된 조직에서 작업 할 때 Owner 속성의 기본 IPerson은 Tycoon 유형이됩니다.


TypeNameHandling.Objects 옵션을 사용하여 사용자 지정된 JsonSerializerSettings 개체를 JsonConvert.SerializeObject ()에 전달하는 대신 앞서 언급 한 것처럼 특정 인터페이스 속성을 특성으로 표시하여 생성 된 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; }
}

최신 버전의 타사 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 변환기가이를 사용하려는 경우 기본 빈 생성자를 넣지 마십시오. 구체적인 유형을 포함하는 생성자를 사용하도록 강제해야합니다.

추신. 이것은 또한 세터를 비공개로 만들 수 있습니다.


같은 문제가 있었기 때문에 알려진 유형 인수를 사용하는 내 변환기를 생각해 냈습니다.

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

I defined two extension methods for deserializing and serializing:

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

You can define your own way of comparing and identifying types in the convertes, i only use class name.


Normally I have always used the solution with TypeNameHandling as suggested by DanielT, but in cases here I have not had control over the incoming JSON (and so cannot ensure that it includes a $type property) I have written a custom converter that just allows you to explicitly specify the concrete type:

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

This just uses the default serializer implementation from Json.Net whilst explicitly specifying the concrete type.

The source code and an overview are available on this blog post.


I just wanted to complete the example that @Daniel T. showed us above:

If you are using this code to serialize your object:

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

The code to deserialize the json should look like this:

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

This is how a json gets conformed when using the TypeNameHandling flag:enter image description here


I've wondered this same thing, but I'm afraid it can't be done.

Let's look at it this way. You hand to JSon.net a string of data, and a type to deserialize into. What is JSON.net to do when it hit's that ISomething? It can't create a new type of ISomething because ISomething is not an object. It also can't create an object that implements ISomething, since it doesn't have a clue which of the many objects that may inherit ISomething it should use. Interfaces, are something that can be automatically serialized, but not automatically deserialized.

What I would do would be to look at replacing ISomething with a base class. Using that you might be able to get the effect you are looking for.


Here is a reference to an article written by ScottGu

Based on that, I wrote some code which I think might be helpful

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

And this is how you would call it

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}

If I understand it correctly, I do not think you need to specify a concrete class which implements the interface for JSON serialization.

참고URL : https://stackoverflow.com/questions/2254872/using-json-net-converters-to-deserialize-properties

반응형