JSON.NET에서 역 직렬화를위한 캐스팅 인터페이스


128

다양한 웹 사이트에서 JSON 개체를 가져 와서 (정보 스크래핑을 생각) C # 개체로 변환하는 리더를 설정하려고합니다. 현재 deserialization 프로세스에 JSON.NET을 사용하고 있습니다. 내가 겪고있는 문제는 클래스에서 인터페이스 수준 속성을 처리하는 방법을 모른다는 것입니다. 그래서 자연의 무언가 :

public IThingy Thing

오류가 발생합니다.

IThingy 유형의 인스턴스를 만들 수 없습니다. 유형은 인터페이스 또는 추상 클래스이며 인스턴스화 할 수 없습니다.

내가 작업하는 코드가 민감하고 단위 테스트가 매우 중요하기 때문에 Thingy와는 반대로 IThingy를 갖는 것이 상대적으로 중요합니다. Thingy와 같은 완전한 객체로는 원자 테스트 스크립트에 대한 객체 조롱이 불가능합니다. 인터페이스 여야합니다.

나는 한동안 JSON.NET의 문서를 살펴 봤는데,이 사이트에서 찾을 수있는 질문은 모두 1 년 전의 것입니다. 도움이 필요하세요?

또한 중요한 경우 내 앱은 .NET 4.0으로 작성되었습니다.


답변:


115

@SamualDavis는 관련 질문 에서 훌륭한 솔루션을 제공했으며 여기에 요약하겠습니다.

인터페이스 속성이있는 구체적인 클래스로 JSON 스트림을 역 직렬화해야하는 경우 구체적인 클래스를 클래스 생성자에 대한 매개 변수로 포함 할 수 있습니다! NewtonSoft deserializer는 속성을 deserialize하기 위해 이러한 구체적인 클래스를 사용해야한다는 것을 알아낼만큼 똑똑합니다.

다음은 그 예입니다.

public class Visit : IVisit
{
    /// <summary>
    /// This constructor is required for the JSON deserializer to be able
    /// to identify concrete classes to use when deserializing the interface properties.
    /// </summary>
    public Visit(MyLocation location, Guest guest)
    {
        Location = location;
        Guest = guest;
    }
    public long VisitId { get; set; }
    public ILocation Location { get;  set; }
    public DateTime VisitDate { get; set; }
    public IGuest Guest { get; set; }
}

15
ICollection과 어떻게 작동합니까? ICollection이 <IGuest> 손님 {얻을; 설정;}
DrSammyD

12
ICollection <ConcreteClass>와 함께 작동하므로 ICollection <Guest>가 작동합니다. FYI와 마찬가지로 생성자에 [JsonConstructor] 속성을 입력하여 여러 생성자가있는 경우 기본적으로 사용하도록 할 수 있습니다
DrSammyD

6
나는 같은 문제에 봉착했다. 제 경우에는 인터페이스의 여러 구현이 있습니다 (귀하의 예에서 인터페이스는 ILocation) .MyLocation, VIPLocation, OrdinaryLocation과 같은 클래스가 있으면 어떨까요? 이것을 Location 속성에 매핑하는 방법은 무엇입니까? MyLocation과 같은 구현이 하나만 있으면 쉽지만 ILocation의 여러 구현이있는 경우 어떻게해야합니까?
ATHER

10
생성자가 두 개 이상인 경우 [JsonConstructor]특성을 사용 하여 특수 생성자를 마크 업할 수 있습니다 .
Dr Rob Lang

26
이것은 전혀 좋지 않습니다. 인터페이스 사용의 요점은 종속성 주입을 사용하는 것이지만 생성자에 필요한 개체 형식 매개 변수를 사용하여 이렇게하면 인터페이스를 속성으로 사용하는 요점이 완전히 망가집니다.
Jérôme MEVEL

57

( 이 질문 에서 복사 )

들어오는 JSON을 제어 할 수없는 경우 (따라서 $ type 속성이 포함되어 있는지 확인할 수없는 경우) 구체적인 유형을 명시 적으로 지정할 수있는 사용자 지정 변환기를 작성했습니다.

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

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

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

public class ConcreteTypeConverter<TConcrete> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        //assume we can convert to anything for now
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        //explicitly specify the concrete type we want to create
        return serializer.Deserialize<TConcrete>(reader);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        //use the default serialization - it works fine
        serializer.Serialize(writer, value);
    }
}

11
저는이 접근법이 정말 마음에 들었고 우리 프로젝트에 적용했습니다. 나는 심지어 ConcreteListTypeConverter<TInterface, TImplementation>유형의 클래스 멤버를 처리하기 위해 추가했습니다 IList<TInterface>.
Oliver

3
그것은 훌륭한 코드입니다. concreteTypeConverter그래도 질문에 실제 코드를 갖는 것이 더 좋을 수 있습니다 .
Chris

2
@Oliver- ConcreteListTypeConverter<TInterface, TImplementation>구현 을 게시 할 수 있습니까 ?
Michael

2
ISomething의 두 구현자가 있다면?
bdaniel7

56

변환기를 사용하는 이유는 무엇입니까? Newtonsoft.Json이 정확한 문제를 해결하기 위한 기본 기능 이 있습니다.

설정 TypeNameHandlingJsonSerializerSettingsTypeNameHandling.Auto

JsonConvert.SerializeObject(
  toSerialize,
  new JsonSerializerSettings()
  {
    TypeNameHandling = TypeNameHandling.Auto
  });

이것은 유형의 구체적인 인스턴스가 아니라 인터페이스 또는 추상 클래스로 유지되는 모든 유형을 json에 넣습니다.

직렬화 및 직렬화 해제에 동일한 설정을 사용하고 있는지 확인하십시오 .

나는 그것을 테스트했으며 목록에서도 매력처럼 작동합니다.

사이트 링크가있는 검색 결과 웹 결과

⚠️ 경고 :

알려진 신뢰할 수있는 소스의 json에만 이것을 사용하십시오. 사용자 snipsnipsnip 은 이것이 실제로 vunerability라고 올바르게 언급했습니다.

자세한 내용은 CA2328SCS0028 을 참조하십시오.


소스 및 대체 수동 구현 : Code Inside 블로그


3
완벽합니다. 이것은 빠르고 더러운 딥 클론에 도움이되었습니다 ( stackoverflow.com/questions/78536/deep-cloning-objects )
Compufreak

1
@Shimmy Objects : "JSON 객체 구조로 직렬화 할 때 .NET 유형 이름을 포함합니다." 자동 : 직렬화되는 개체의 유형이 선언 된 유형과 동일하지 않은 경우 .NET 유형 이름을 포함합니다. 여기에는 기본적으로 직렬화 된 루트 객체가 포함되지 않습니다. JSON에 루트 객체의 유형 이름을 포함하려면 SerializeObject (Object, Type, JsonSerializerSettings) 또는 Serialize (JsonWriter, Object, Type)를 사용하여 루트 유형 객체를 지정해야합니다. "출처 : newtonsoft.com/json/help/html/…
Mafii

4
Deserialization에서 이것을 시도했지만 작동하지 않습니다. 이 스택 오버플로 질문의 제목은 "JSON.NET의 직렬화 복원을위한 인터페이스를 주조"입니다
저스틴 루소

3
@JustinRusso 그것은 json이 동일한 설정으로 직렬화되었을 때만 작동합니다
Mafii

3
더럽지는 않지만 빠른 솔루션에 찬성 투표하십시오. 구성을 직렬화하는 중이면 작동합니다. 변환기를 구축하기 위해 개발 중단을 능가하고 주입 된 모든 속성을 장식하는 것보다 확실히 능가합니다. serializer.TypeNameHandling = TypeNameHandling.Auto; JsonConvert.DefaultSettings (). TypeNameHandling = TypeNameHandling.Auto;
Sean Anderson

39

여러 인터페이스 구현의 역 직렬화를 활성화하려면 JsonConverter를 사용할 수 있지만 속성을 통해서는 사용할 수 없습니다.

Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Converters.Add(new DTOJsonConverter());
Interfaces.IEntity entity = serializer.Deserialize(jsonReader);

DTOJsonConverter는 구체적인 구현으로 각 인터페이스를 매핑합니다.

class DTOJsonConverter : Newtonsoft.Json.JsonConverter
{
    private static readonly string ISCALAR_FULLNAME = typeof(Interfaces.IScalar).FullName;
    private static readonly string IENTITY_FULLNAME = typeof(Interfaces.IEntity).FullName;


    public override bool CanConvert(Type objectType)
    {
        if (objectType.FullName == ISCALAR_FULLNAME
            || objectType.FullName == IENTITY_FULLNAME)
        {
            return true;
        }
        return false;
    }

    public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        if (objectType.FullName == ISCALAR_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientScalar));
        else if (objectType.FullName == IENTITY_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientEntity));

        throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
    }

    public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

DTOJsonConverter는 deserializer에만 필요합니다. 직렬화 프로세스는 변경되지 않습니다. Json 객체는 구체적인 유형 이름을 포함 할 필요가 없습니다.

SO 게시물 은 일반적인 JsonConverter를 사용하여 동일한 솔루션을 한 단계 더 제공합니다.


Serializer.Serialize에 대한 WriteJson 메서드의 호출은 변환기에 의해 직렬화되는 값에 대해 serialize를 호출하면 변환기의 WriteJson 메서드가 재귀 적으로 다시 호출되기 때문에 스택 오버플로가 발생하지 않습니까?
Triynko

CanConvert () 메서드가 일관된 결과를 반환하면 안됩니다.
Eric Boumendil 2014 년

3
FullName유형을 직접 비교할 수 있는데 왜 s 를 비교 합니까?
Alex Zhukovskiy

유형을 비교하는 것도 괜찮습니다.
Eric Boumendil

23

추상 유형을 실제 유형에 매핑하려면이 클래스를 사용합니다.

public class AbstractConverter<TReal, TAbstract> : JsonConverter where TReal : TAbstract
{
    public override Boolean CanConvert(Type objectType) 
        => objectType == typeof(TAbstract);

    public override Object ReadJson(JsonReader reader, Type type, Object value, JsonSerializer jser) 
        => jser.Deserialize<TReal>(reader);

    public override void WriteJson(JsonWriter writer, Object value, JsonSerializer jser) 
        => jser.Serialize(writer, value);
}

... 역 직렬화 할 때 :

        var settings = new JsonSerializerSettings
        {
            Converters = {
                new AbstractConverter<Thing, IThingy>(),
                new AbstractConverter<Thing2, IThingy2>()
            },
        };

        JsonConvert.DeserializeObject(json, type, settings);

1
나는 내 문제를 해결하는 훌륭하고 간결한 대답을 정말 좋아합니다. autofac 또는 아무것도 필요하지 않습니다!
벤 전원

3
이것을 변환기 클래스 선언에 넣을 가치가 where TReal : TAbstract있습니다. 유형으로 캐스트 할 수 있는지 확인하기 위해
Artemious

1
더 완벽 할 수 있습니다 where TReal : class, TAbstract, new().
Erik Philips

2
이 변환기도 구조체와 함께 사용했는데 "where TReal : TAbstract"로 충분하다고 생각합니다. 모두 감사합니다.
Gildor

2
금! 가는 길.
SwissCoder

12

Nicholas Westby는 멋진 기사 에서 훌륭한 솔루션을 제공했습니다 .

다음과 같은 인터페이스를 구현하는 가능한 많은 클래스 중 하나로 JSON을 역 직렬화하려는 경우 :

public class Person
{
    public IProfession Profession { get; set; }
}

public interface IProfession
{
    string JobTitle { get; }
}

public class Programming : IProfession
{
    public string JobTitle => "Software Developer";
    public string FavoriteLanguage { get; set; }
}

public class Writing : IProfession
{
    public string JobTitle => "Copywriter";
    public string FavoriteWord { get; set; }
}

public class Samples
{
    public static Person GetProgrammer()
    {
        return new Person()
        {
            Profession = new Programming()
            {
                FavoriteLanguage = "C#"
            }
        };
    }
}

사용자 지정 JSON 변환기를 사용할 수 있습니다.

public class ProfessionConverter : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(IProfession);
    }
    public override void WriteJson(JsonWriter writer,
        object value, JsonSerializer serializer)
    {
        throw new InvalidOperationException("Use default serialization.");
    }

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var profession = default(IProfession);
        switch (jsonObject["JobTitle"].Value())
        {
            case "Software Developer":
                profession = new Programming();
                break;
            case "Copywriter":
                profession = new Writing();
                break;
        }
        serializer.Populate(jsonObject.CreateReader(), profession);
        return profession;
    }
}

그리고 "Profession"속성을 JsonConverter 속성으로 장식하여 사용자 지정 변환기를 사용하도록 알려야합니다.

    public class Person
    {
        [JsonConverter(typeof(ProfessionConverter))]
        public IProfession Profession { get; set; }
    }

그런 다음 인터페이스를 사용하여 클래스를 캐스팅 할 수 있습니다.

Person person = JsonConvert.DeserializeObject<Person>(jsonString);

8

시도해 볼 수있는 두 가지 :

try / parse 모델을 구현합니다.

public class Organisation {
  public string Name { get; set; }

  [JsonConverter(typeof(RichDudeConverter))]
  public IPerson Owner { get; set; }
}

public interface IPerson {
  string Name { get; set; }
}

public class Tycoon : IPerson {
  public string Name { get; set; }
}

public class Magnate : IPerson {
  public string Name { get; set; }
  public string IndustryName { get; set; }
}

public class Heir: IPerson {
  public string Name { get; set; }
  public IPerson Benefactor { get; set; }
}

public class RichDudeConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return (objectType == typeof(IPerson));
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    // pseudo-code
    object richDude = serializer.Deserialize<Heir>(reader);

    if (richDude == null)
    {
        richDude = serializer.Deserialize<Magnate>(reader);
    }

    if (richDude == null)
    {
        richDude = serializer.Deserialize<Tycoon>(reader);
    }

    return richDude;
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

또는 개체 모델에서 그렇게 할 수있는 경우 IPerson과 리프 개체간에 구체적인 기본 클래스를 구현하고 역 직렬화합니다.

첫 번째는 잠재적으로 런타임에 실패 할 수 있고 두 번째는 개체 모델을 변경해야하며 출력을 가장 낮은 공통 분모로 균질화합니다.


작업해야하는 규모로 인해 try / parse 모델을 사용할 수 없습니다. 자주 발생하는 임베디드 JSON 개체를 나타 내기 위해 수백 개의 스텁 / 도우미 개체가 포함 된 수백 개의 기본 개체 범위를 고려해야합니다. 개체 모델을 변경하는 것은 당연한 일이 아니지만 속성에서 구체적인 기본 클래스를 사용하면 단위 테스트를 위해 항목을 모의 할 수 없게 될까요? 아니면 어떻게 든 뒤로 돌아가고 있습니까?
tmesser 2011

IPerson에서 모의를 구현할 수 있습니다. Organisation.Owner 속성의 유형은 여전히 ​​IPerson입니다. 그러나 임의의 대상을 역 직렬화하려면 구체적인 유형을 반환해야합니다. 유형 정의를 소유하지 않고 코드에 필요한 최소 속성 집합을 정의 할 수없는 경우 마지막 수단은 키 / 값 모음과 같습니다. Facebook 예제 댓글 사용-ILocation의 (하나 또는 여러) 구현이 어떻게 생겼는지에 대한 답변을 게시 할 수 있습니까? 그것은 일을 진전시키는 데 도움이 될 수 있습니다.
mcw 2011

주된 희망은 조롱이기 때문에 ILocation 인터페이스는 실제로 Location 콘크리트 객체의 외관 일뿐입니다. 난 그냥 흥분 빠른 예는이 (같은 것 pastebin.com/mWQtqGnB 인터페이스이 (에 대한) pastebin.com/TdJ6cqWV 구체적인 객체).
tmesser 2011

다음 단계로 넘어 가기 위해 이것은 IPage가 어떻게 생겼는지 ( pastebin.com/iuGifQXp ) 및 Page ( pastebin.com/ebqLxzvm )의 예입니다. 물론 문제는 Page의 deserialization이 일반적으로 잘 작동하지만 ILocation 속성에 도달하면 질식한다는 것입니다.
tmesser 2011

좋아, 실제로 스크래핑 및 역 직렬화하는 객체에 대해 생각하면 일반적으로 JSON 데이터가 단일 구체적인 클래스 정의와 일치하는 경우입니까? 의미 (가설 적으로) 역 직렬화 된 개체의 구체적인 유형으로 Location을 사용하기에 부적합하게 만드는 추가 속성이있는 "위치"를 만나지 않을 것입니까? 그렇다면 "LocationConverter"를 사용하여 Page의 ILocation 속성을 어트 리뷰 션하는 것이 작동합니다. 그렇지 않다면 JSON 데이터가 항상 견고하거나 일관된 구조 (예 : ILocation)를 따르지 않기 때문입니다. (... 계속)
mcw

8

나는 이것이 유용하다는 것을 알았다. 당신도 할 수 있습니다.

사용 예

public class Parent
{
    [JsonConverter(typeof(InterfaceConverter<IChildModel, ChildModel>))]
    IChildModel Child { get; set; }
}

커스텀 생성 변환기

public class InterfaceConverter<TInterface, TConcrete> : CustomCreationConverter<TInterface>
    where TConcrete : TInterface, new()
{
    public override TInterface Create(Type objectType)
    {
        return new TConcrete();
    }
}

Json.NET 문서


1
실행 가능한 솔루션이 아닙니다. 목록을 다루지 않고 모든 곳에 장식 자 / 주석을 뿌립니다.
Sean Anderson

5

Oliver가 참조한 ConcreteListTypeConverter에 대해 궁금한 사람들을 위해 다음은 내 시도입니다.

public class ConcreteListTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface 
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var res = serializer.Deserialize<List<TImplementation>>(reader);
        return res.ConvertAll(x => (TInterface) x);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

1
나는 재정의 된 CanConvert(Type objectType) { return true;}. 이게 정확히 얼마나 도움이 되나요? 내가 틀렸을 지 모르지만 경험이없는 작은 선수에게 상대와 상관없이 싸움에서 이길 것이라고 말하는 것과 같지 않습니까?
Chef_Code

4

그만한 가치가 있기 때문에 대부분의 경우이 문제를 직접 처리해야했습니다. 각 개체에는 Deserialize (string jsonStream) 메서드가 있습니다. 몇 가지 스 니펫 :

JObject parsedJson = this.ParseJson(jsonStream);
object thingyObjectJson = (object)parsedJson["thing"];
this.Thing = new Thingy(Convert.ToString(thingyObjectJson));

이 경우 new Thingy (string) 는 적절한 구체적인 유형 의 Deserialize (string jsonStream) 메서드를 호출하는 생성자입니다 . 이 체계는 json.NET이 처리 할 수있는 기준점에 도달 할 때까지 계속 아래로 내려갑니다.

this.Name = (string)parsedJson["name"];
this.CreatedTime = DateTime.Parse((string)parsedJson["created_time"]);

등등. 이 설정을 통해 라이브러리 자체의 많은 부분을 리팩터링하지 않고도 처리 할 수있는 json.NET 설정을 제공하거나 관련된 개체의 수로 인해 전체 라이브러리를 방해 할 수있는 다루기 힘든 시도 / 분석 모델을 사용할 수있었습니다. 또한 특정 객체에 대한 json 변경 사항을 효과적으로 처리 할 수 ​​있으며 객체가 닿는 모든 것에 대해 걱정할 필요가 없음을 의미합니다. 결코 이상적인 솔루션은 아니지만 유닛 및 통합 테스트에서 꽤 잘 작동합니다.


4

다음과 같은 autofac 설정을 가정하십시오.

public class AutofacContractResolver : DefaultContractResolver
{
    private readonly IContainer _container;

    public AutofacContractResolver(IContainer container)
    {
        _container = container;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);

        // use Autofac to create types that have been registered with it
        if (_container.IsRegistered(objectType))
        {
           contract.DefaultCreator = () => _container.Resolve(objectType);
        }  

        return contract;
    }
}

그런 다음 클래스가 다음과 같다고 가정합니다.

public class TaskController
{
    private readonly ITaskRepository _repository;
    private readonly ILogger _logger;

    public TaskController(ITaskRepository repository, ILogger logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public ITaskRepository Repository
    {
        get { return _repository; }
    }

    public ILogger Logger
    {
        get { return _logger; }
    }
}

따라서 역 직렬화에서 해결 프로그램의 사용법은 다음과 같을 수 있습니다.

ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<TaskRepository>().As<ITaskRepository>();
builder.RegisterType<TaskController>();
builder.Register(c => new LogService(new DateTime(2000, 12, 12))).As<ILogger>();

IContainer container = builder.Build();

AutofacContractResolver contractResolver = new AutofacContractResolver(container);

string json = @"{
      'Logger': {
        'Level':'Debug'
      }
}";

// ITaskRespository and ILogger constructor parameters are injected by Autofac 
TaskController controller = JsonConvert.DeserializeObject<TaskController>(json, new JsonSerializerSettings
{
    ContractResolver = contractResolver
});

Console.WriteLine(controller.Repository.GetType().Name);

http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm 에서 자세한 내용을 볼 수 있습니다 .


나는 이것을 최고의 솔루션으로 찬성 할 것입니다. DI는 요즘 C # 웹 개발자들에 의해 매우 널리 사용되어 왔으며, 이는 리졸버에 의한 이러한 유형 변환을 처리하는 중앙 집중식 장소로 적합합니다.
appletwo jul.

3

어떤 객체는 것이다 이제까지 수 없습니다 인터페이스는 모든 정의에 의해 추상적입니다 같은 IThingy을.

처음 직렬화 된 객체 는 추상 인터페이스를 구현하는 구체적인 유형 이었습니다 . 직렬화 된 데이터를 되살리는 동일한 구체적인 클래스 가 필요합니다 .

결과 객체 는 찾고 있는 추상 인터페이스를 구현 하는 어떤 유형이 됩니다.

로부터 문서 당신이 사용할 수있는 다음과

(Thingy)JsonConvert.DeserializeObject(jsonString, typeof(Thingy));

구체적인 유형에 대해 JSON.NET에 알리기 위해 역 직렬화 할 때.


이것이 제가 언급 한 1 년 전의 게시물입니다. 유일한 주요 제안 (사용자 지정 변환기 작성)은 제가 고려해야하는 규모로는별로 실현 가능하지 않습니다. JSON.NET은 그 사이에 많은 변화를 겪었습니다. 클래스와 인터페이스의 차이점을 완벽하게 이해하지만 C #은 입력과 관련하여 인터페이스에서 인터페이스를 구현하는 개체로의 암시 적 변환도 지원합니다. 이 인터페이스를 구현할 객체를 JSON.NET에 알리는 방법이 있는지 본질적으로 묻습니다.
tmesser 2011

내가 당신에게 지적한 대답은 전부였습니다. _type사용할 구체적인 유형을 나타내는 속성이 있는지 확인하세요 .
Sean Kinsey 2011

그리고 나는 C #이 인터페이스로 선언 된 변수에서 어떤 종류의 힌트도없이 구체적인 유형으로의 '암시 적'유형 캐스팅을 지원한다는 사실을 강력히 의심합니다.
Sean Kinsey 2011

내가 잘못 읽지 않는 한 _type 속성은 직렬화 될 JSON에 있어야합니다. 이미 직렬화 한 것만 역 직렬화하는 경우에는 잘 작동하지만 여기서 진행되는 것은 아닙니다. 그 표준을 따르지 않을 여러 사이트에서 JSON을 가져오고 있습니다.
tmesser 2011

@YYY-소스 JSON의 직렬화와 역 직렬화를 모두 제어합니까? 궁극적으로 직렬화 된 JSON에 구체적인 유형을 역 직렬화 할 때 사용하는 힌트로 포함해야하거나 런타임에 구체적인 유형을 감지 / 감지 시도하는 시도 / 분석 모델을 사용해야하기 때문입니다. 적절한 deserializer를 호출합니다.
mcw 2011

3

이것은 훌륭하게 일반적이기 때문에 내가 좋아하는 이것에 대한 나의 해결책은 다음과 같습니다.

/// <summary>
/// Automagically convert known interfaces to (specific) concrete classes on deserialisation
/// </summary>
public class WithMocksJsonConverter : JsonConverter
{
    /// <summary>
    /// The interfaces I know how to instantiate mapped to the classes with which I shall instantiate them, as a Dictionary.
    /// </summary>
    private readonly Dictionary<Type,Type> conversions = new Dictionary<Type,Type>() { 
        { typeof(IOne), typeof(MockOne) },
        { typeof(ITwo), typeof(MockTwo) },
        { typeof(IThree), typeof(MockThree) },
        { typeof(IFour), typeof(MockFour) }
    };

    /// <summary>
    /// Can I convert an object of this type?
    /// </summary>
    /// <param name="objectType">The type under consideration</param>
    /// <returns>True if I can convert the type under consideration, else false.</returns>
    public override bool CanConvert(Type objectType)
    {
        return conversions.Keys.Contains(objectType);
    }

    /// <summary>
    /// Attempt to read an object of the specified type from this reader.
    /// </summary>
    /// <param name="reader">The reader from which I read.</param>
    /// <param name="objectType">The type of object I'm trying to read, anticipated to be one I can convert.</param>
    /// <param name="existingValue">The existing value of the object being read.</param>
    /// <param name="serializer">The serializer invoking this request.</param>
    /// <returns>An object of the type into which I convert the specified objectType.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            return serializer.Deserialize(reader, this.conversions[objectType]);
        }
        catch (Exception)
        {
            throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
        }
    }

    /// <summary>
    /// Not yet implemented.
    /// </summary>
    /// <param name="writer">The writer to which I would write.</param>
    /// <param name="value">The value I am attempting to write.</param>
    /// <param name="serializer">the serializer invoking this request.</param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

}

conversions 인스턴스 변수를 인스턴스화하는 데 사용할 Dictionary <Type, Type> 유형의 인수를 사용하는 생성자를 추가하여보다 일반적인 변환기로 명확하고 사소하게 변환 할 수 있습니다.


3

몇 년 후 비슷한 문제가 발생했습니다. 제 경우에는 중첩 된 인터페이스가 많았고 런타임에 구체적인 클래스를 생성하여 제네릭 클래스에서 작동하도록 선호했습니다.

런타임에 Newtonsoft에서 반환 한 개체를 래핑하는 프록시 클래스를 만들기로 결정했습니다.

이 접근 방식의 장점은 클래스의 구체적인 구현이 필요하지 않으며 중첩 된 인터페이스의 깊이를 자동으로 처리 할 수 ​​있다는 것입니다. 내 블로그 에서 더 많은 것을 볼 수 있습니다 .

using Castle.DynamicProxy;
using Newtonsoft.Json.Linq;
using System;
using System.Reflection;

namespace LL.Utilities.Std.Json
{
    public static class JObjectExtension
    {
        private static ProxyGenerator _generator = new ProxyGenerator();

        public static dynamic toProxy(this JObject targetObject, Type interfaceType) 
        {
            return _generator.CreateInterfaceProxyWithoutTarget(interfaceType, new JObjectInterceptor(targetObject));
        }

        public static InterfaceType toProxy<InterfaceType>(this JObject targetObject)
        {

            return toProxy(targetObject, typeof(InterfaceType));
        }
    }

    [Serializable]
    public class JObjectInterceptor : IInterceptor
    {
        private JObject _target;

        public JObjectInterceptor(JObject target)
        {
            _target = target;
        }
        public void Intercept(IInvocation invocation)
        {

            var methodName = invocation.Method.Name;
            if(invocation.Method.IsSpecialName && methodName.StartsWith("get_"))
            {
                var returnType = invocation.Method.ReturnType;
                methodName = methodName.Substring(4);

                if (_target == null || _target[methodName] == null)
                {
                    if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                    {

                        invocation.ReturnValue = null;
                        return;
                    }

                }

                if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                {
                    invocation.ReturnValue = _target[methodName].ToObject(returnType);
                }
                else
                {
                    invocation.ReturnValue = ((JObject)_target[methodName]).toProxy(returnType);
                }
            }
            else
            {
                throw new NotImplementedException("Only get accessors are implemented in proxy");
            }

        }
    }



}

용법:

var jObj = JObject.Parse(input);
InterfaceType proxyObject = jObj.toProxy<InterfaceType>();

감사! 이것은 들어오는 json에 대한 제한을 강요하지 않고 동적 타이핑 (덕 타이핑)을 올바르게 지원하는 유일한 대답입니다.
Philip Pittle 2017 년

문제 없어요. 나는 거기에 아무것도 없다는 것을보고 조금 놀랐다. 원래 예제 이후로 약간 이동했기 때문에 코드를 공유하기로 결정했습니다. github.com/sudsy/JsonDuckTyper . 또한 nuget에 JsonDuckTyper로 게시했습니다. 향상시키고 싶다면 PR을 보내 주시면 기꺼이 도와 드리겠습니다.
Sudsy

이 영역에서 솔루션을 찾고있을 때 github.com/ekonbenefits/impromptu-interface 도 발견했습니다. dotnet core 1.0을 지원하지 않기 때문에 제 경우에는 작동하지 않지만 작동 할 수 있습니다.
Sudsy

Impromptu Interface를 사용해 보았지만 Json.Net은 Impromptu Interface에서 PopulateObject생성 한 프록시에 만족하지 않았습니다 . 불행히도 Duck Typing을 포기했습니다. 리플렉션을 사용하여 요청 된 인터페이스의 기존 구현을 찾고이를 사용하는 사용자 지정 Json Contract Serializer를 만드는 것이 더 쉬웠습니다.
Philip Pittle

1

JsonKnownTypes를 사용하면 사용하는 것과 매우 유사하며 json에 판별자를 추가합니다.

[JsonConverter(typeof(JsonKnownTypeConverter<Interface1>))]
[JsonKnownType(typeof(MyClass), "myClass")]
public interface Interface1
{  }
public class MyClass : Interface1
{
    public string Something;
}

이제 json에서 객체를 직렬화하면 값 이 추가 "$type"되고 "myClass"deserialize에 사용됩니다.

Json :

{"Something":"something", "$type":"derived"}

0

내 솔루션은 생성자에 인터페이스 요소가 추가되었습니다.

public class Customer: ICustomer{
     public Customer(Details details){
          Details = details;
     }

     [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
     public IDetails Details {get; set;}
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.