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


88

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

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

    public ISomething Thing { get; set; }
}

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

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


ISomething을 가져 오거나 설정하는 속성 이름을 지정해야한다고 생각합니다
ram

나는 가지고있다. C # 3.5에 도입 된 자동 구현 속성에 대한 속기를 사용하고 있습니다. msdn.microsoft.com/en-us/library/bb384054.aspx
dthrasher 2010

4
ISomething 유형이 아닙니다. 램이 맞다고 생각하지만 여전히 속성 이름이 필요합니다. 이것이 귀하의 문제와 관련이 없다는 것을 알고 있지만 위의 귀하의 의견으로 인해 이름없이 속성을 지정할 수있는 .NET의 새로운 기능이 누락되었다고 생각했습니다.
Mr Moose

답변:


92

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을 사용한 직렬화 설정


흥미 롭군. 나는 이것을 가지고 놀아야 할 것입니다. 좋은 팁!
dthrasher 2010 년

2
Newtonsoft.Json의 경우는 유사한 작동하지만 속성은 "$ 유형"입니다
야프

너무 쉬웠어요!
Shimmy Weitzhandler

1
를 사용할 때 여기에서 가능한 보안 문제를 확인하십시오 TypeNameHandling. 자세한 내용 은 Newtonsoft Json의 TypeNameHandling주의를 참조 하십시오.
dbc

나는 어제 변환기와 미친 듯이 고생했고 이것이 훨씬 더 좋고 더 이해하기 쉬웠습니다.
Horothenic

52

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 유형이됩니다.


아주 좋아. 변환기를 사용해 보겠습니다.
dthrasher 2010

4
"[JsonConverter (typeof (TycoonConverter))]"태그가 인터페이스 목록에 있으면 계속 작동합니까?
Zwik 2014

40

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

훌륭한. 감사합니다 :)
Darren Young

5
인터페이스 또는 추상 클래스 컬렉션의 경우 속성은 "ItemTypeNameHandling"입니다. 예 : [JsonProperty (ItemTypeNameHandling = TypeNameHandling.Auto)]
Anthony F

감사합니다!
brudert

24

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

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


6
이것은 옥상에서 외쳐야합니다! 사실, 구체적인 구현에 제약을 추가하지만, 사용할 수있는 상황에 대한 다른 접근 방식보다 훨씬 더 간단합니다.
Mark Meuer 2013-08-09

3
여러 구체적인 유형을 가진 생성자가 두 개 이상있는 경우에도 여전히 알 수 있습니까?
Teoman shipahi

1
이 대답은 그렇지 않으면해야 할 모든 복잡한 넌센스에 비해 너무 우아합니다. 이것은 받아 들여진 대답이어야합니다. 하지만 제 경우 한 가지주의 할 점은 생성자 앞에 [JsonConstructor]를 추가해야 작동한다는 것입니다 .... 콘크리트 생성자 중 하나에서만 이것을 사용하면 (4 년 된) 문제가 해결 될 것이라고 생각합니다. @Teomanshipahi
nacitar sevaht

@nacitarsevaht 나는 돌아가서 지금 내 문제를 해결할 수 있습니다 :) 어쨌든 나는 그것이 무엇인지 기억조차 못하지만 이것이 특정 경우에 좋은 해결책입니다.
Teoman shipahi

우리는 이것도 사용하지만, 대부분의 경우 변환을 선호합니다. 왜냐하면 구체적인 유형을 생성자에 결합하면 처음에 속성에 대한 인터페이스를 사용하는 목적이 무너지기 때문입니다!
gabe

19

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

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

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


1
이 JsonConverter는 훌륭합니다. 사용했지만이 방법으로 해결 한 몇 가지 문제에 직면했습니다. -리플렉션을 사용하여 생성자를 검색하고 Create () 메서드에서 인스턴스화
Aurel

3

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

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

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

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


1
이것은 훌륭한 솔루션입니다. 건배.
JohnMetta

2

@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이 준수되는 방법입니다 .여기에 이미지 설명 입력


-5

나는 이와 똑같은 것을 궁금해했지만 할 수 없을 것 같습니다.

이런 식으로 살펴 보겠습니다. JSon.net에 데이터 문자열과 역 직렬화 할 유형을 전달합니다. ISomething을 쳤을 때 할 JSON.net은 무엇입니까? ISomething은 개체가 아니기 때문에 새로운 유형의 ISomething을 만들 수 없습니다. 또한 ISomething을 구현하는 개체를 만들 수 없습니다. ISomething을 상속 할 수있는 많은 개체 중 어떤 것을 사용해야하는지에 대한 단서가 없기 때문입니다. 인터페이스는 자동으로 직렬화 될 수 있지만 자동으로 직렬화 해제되지는 않습니다.

내가 할 일은 ISomething을 기본 클래스로 대체하는 것입니다. 그것을 사용하면 원하는 효과를 얻을 수 있습니다.


1
나는 그것이 "즉시"작동하지 않을 것이라는 것을 알고 있습니다. 하지만 구체적인 클래스를 제공하기 위해 사용할 수있는 "[JsonProperty (typeof (SomethingBase))]"와 같은 속성이 있는지 궁금합니다.
dthrasher 2010

그렇다면 위 코드에서 ISomething 대신 SomethingBase를 사용하지 않는 이유는 무엇입니까? 인터페이스는 단순히 주어진 클래스와 통신 "인터페이스"를 정의하기 때문에 직렬화에 사용되어서는 안되므로 우리가 이것을 잘못된 방식으로보고 있다고 주장 할 수 있습니다. 기술적으로 인터페이스를 직렬화하는 것은 추상 클래스를 직렬화하는 것처럼 말도 안됩니다. 그래서 "수행 될 수있는"동안 나는 "수행 될 수없는 것"이라고 주장합니다.
Timothy Baldridge

Newtonsoft.Json.Serialization 네임 스페이스의 클래스를 살펴 보셨습니까? 특히 JsonObjectContract 클래스?
johnny

-9

다음은 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 직렬화를위한 인터페이스를 구현하는 구체적인 클래스를 지정할 필요가 없다고 생각합니다.


1
샘플은 .NET Framework의 클래스 인 JavaScriptSerializer를 사용합니다. 내 시리얼 라이저로 Json.NET을 사용하고 있습니다. codeplex.com/Json
dthrasher 2010

3
원래 질문을 참조하지 않고 Json.NET이 명시 적으로 언급되었습니다.
Oliver
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.