[.net] 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();
    }
}

역 직렬화 및 직렬화를위한 두 가지 확장 메서드를 정의했습니다.

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

개종자의 유형을 비교하고 식별하는 자신의 방법을 정의 할 수 있습니다. 저는 클래스 이름 만 사용합니다.


답변

일반적으로 TypeNameHandlingDanielT에서 제안한대로 솔루션을 항상 사용 했지만 여기서는 들어오는 JSON을 제어하지 않았기 때문에 $type속성 이 포함되어 있는지 확인할 수 없습니다. 명시 적으로 지정할 수있는 사용자 지정 변환기를 작성했습니다. 구체적인 유형 :

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

이것은 구체적 유형을 명시 적으로 지정하는 동안 Json.Net의 기본 직렬 변환기 구현을 사용합니다.

소스 코드와 개요는 이 블로그 게시물 에서 볼 수 있습니다 .


답변

@Daniel T.가 위에서 보여준 예제를 완성하고 싶었습니다.

이 코드를 사용하여 객체를 직렬화하는 경우 :

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

json을 deserialize하는 코드는 다음과 같습니다.

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

이것은 TypeNameHandling플래그를 사용할 때 json이 준수되는 방법입니다 .여기에 이미지 설명 입력