2017-03-27 6 views
2

나는 다음과 같은 형식으로 JSON을 가지고Json.NET을 사용하여 참조 용 사용자 정의 형식을 사용할 때 참조로 객체를 비 직렬화 할 수 있습니까?

{ 
    "users": [ 
     { 
      "first_name": "John", 
      "last_name": "Smith", 
      "vet": [ "FOO", "VET-1" ], 
      "animals": [ [ "FOO", "ANIMAL-22" ] ] 
     }, 
     { 
      "first_name": "Susan", 
      "last_name": "Smith", 
      "vet": [ "FOO", "VET-1" ] 
     } 
    ], 
    "BAR": { 
     "VET-1": { 
      "vet_name": "Acme, Inc", 
      "vet_id": 456 
     }, 
     "ANIMAL-22": { 
      "animal_name": "Fido", 
      "species": "dog", 
      "animal_id": 789, 
      "vet": [ "FOO", "VET-1" ] 
     } 
    } 
} 

일부 중첩 된 개체 또는 두 번 이상 참조 된 개체를 참조로 연재됩니다.

참조 된 개체는 JSON 개체의 끝에있는 BAR 배열에 포함되어 있으며 [ "FOO", "ANIMAL-22" ] 배열로 식별됩니다.

(모두 FOOBAR 정적 상수, 그리고 ANIMAL-22/VET-1 식별자는 반 랜덤)

불행하게도,이 일치하지 않는 방법 Json.NET 이미 serializes/deserializes referenced objects 내가하지 않는 것 구현할 수 IReferenceResolver 내가 행동을 충분히 조절할 수있게해라. (처음에는 it's fixed to use "$ref").

영향을받는 속성에 대한 사용자 지정 JsonConverter 작성을 시도했지만 루트 개체의 BAR 속성에 대한 참조를 얻지 못하는 것 같습니다.

Json.NET을 위의 JSON을 위와 같은 C# 클래스 구조로 역 직렬화 할 수있는 방법이 있습니까?

public class User 
{ 
    [JsonProperty("first_name")] 
    public string FirstName { get; set; } 

    [JsonProperty("last_name")] 
    public string LastName { get; set; } 

    [JsonProperty("vet")] 
    public Vet Vet { get; set; } 

    [JsonProperty("animals")] 
    public List<Animal> Animals { get; set; } 
} 

public class Vet 
{ 
    [JsonProperty("vet_id")] 
    public int Id { get; set; } 

    [JsonProperty("vet_name")] 
    public string Name { get; set; } 
} 

public class Animal 
{ 
    [JsonProperty("animal_id")] 
    public int Id { get; set; } 

    [JsonProperty("animal_name")] 
    public string Name { get; set; } 

    [JsonProperty("vet")] 
    public Vet Vet { get; set; } 

    [JsonProperty("species")] 
    public string Species { get; set; } 
} 

편집 # 1 : 난 단지 Animal 내 예 Vet 제공하지만, 거기에 이런 식으로 참조 유형의 많은 수이며, 나는이 '일반'또는 유형을 필요가 있다고 생각 - 배열 구조 [ "FOO", "..." ]의 그런 발생을 처리 할 수있는 솔루션은 각 C# 유형을 개별적으로 코딩 할 필요가 없습니다.

+0

아니, 당신이 자동으로 수행 할 수 없습니다. Json.NET은 '$ ref'속성 이름의 하드 코딩을 제쳐 놓고 싱글 패스 시리얼 라이저이지만''ANIMAL-22 ''와''VET-1''은 JSON에 대한 * forward references * 나중에 파일에 나타나는 개체는 참조를 읽을 때 읽히지 않습니다. 이러한 참조를 수정하기 위해 직렬화 해제 후에 객체를 수동으로 후 처리하거나 LINQ to JSON을 사용하여 Json.NET의 필수 형식으로 JSON을 전처리해야합니다. – dbc

답변

2

@dbc가 주석에서 언급했듯이 Json.Net이 사용자 정의 참조 형식을 자동으로 처리하도록하는 쉬운 방법은 없습니다. 즉, 수는입니다. LINQ-to-JSON (JObjects)을 사용하여 JSON을 구문 분석하고 JsonConverter 및 몇 개의 사전을 사용하여 참조를 확인하고 클래스를 채우면서 Json.Net에 과도한 부하를 남깁니다. 여기에 내가 걸릴 것 접근 방식 :

  1. [ "FOO", "<key>" ] 참조 형식을 디코딩하고 제공된 사전에서 해당 개체를 반환 할 수 있습니다 JsonConverter 일반적인 정의를 작성합니다.

    public class ReferenceConverter<T> : JsonConverter 
    { 
        private Dictionary<string, T> ReferenceDict { get; set; } 
    
        public ReferenceConverter(Dictionary<string, T> referenceDict) 
        { 
         ReferenceDict = referenceDict; 
        } 
    
        public override bool CanConvert(Type objectType) 
        { 
         return objectType == typeof(T); 
        } 
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
        { 
         JArray array = JArray.Load(reader); 
         if (array.Count == 2 && 
          array[0].Type == JTokenType.String && 
          (string)array[0] == "FOO" && 
          array[1].Type == JTokenType.String) 
         { 
          string key = (string)array[1]; 
          T obj; 
          if (ReferenceDict.TryGetValue(key, out obj)) 
           return obj; 
    
          throw new JsonSerializationException("No " + typeof(T).Name + " was found with the key \"" + key + "\"."); 
         } 
    
         throw new JsonSerializationException("Reference had an invalid format: " + array.ToString()); 
        } 
    
        public override bool CanWrite 
        { 
         get { return false; } 
        } 
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
        { 
         throw new NotImplementedException(); 
        } 
    } 
    
  2. JObject로 JSON을 구문 분석하고 JSON의 BAR 섹션에서 Vets의 사전을 구축 : 여기 컨버터에 대한 코드입니다.
    JObject data = JObject.Parse(json); 
    
    Dictionary<string, Vet> vets = data["BAR"] 
        .Children<JProperty>() 
        .Where(jp => jp.Value["vet_id"] != null) 
        .ToDictionary(jp => jp.Name, jp => jp.Value.ToObject<Vet>()); 
    
  3. Vet 참조를 해결하기 위해 ReferenceConverter<T> 2 단계에서 사전을 사용하여 JSON의 BAR 섹션에서 Animals의 사전을 구축 할 수 있습니다.

    JsonSerializer serializer = new JsonSerializer(); 
    serializer.Converters.Add(new ReferenceConverter<Vet>(vets)); 
    
    Dictionary<string, Animal> animals = data["BAR"] 
        .Children<JProperty>() 
        .Where(jp => jp.Value["animal_id"] != null) 
        .ToDictionary(jp => jp.Name, jp => jp.Value.ToObject<Animal>(serializer)); 
    
  4. 마지막으로, 다시 (그래서 실제로 두 개의 컨버터 인스턴스 지금, 사전 당 하나) 모든 참조를 해결하기 위해 ReferenceConverter<T> 플러스 두 개의 사전을 사용하여 JSON에서 users 목록을 직렬화. 여기

    serializer.Converters.Add(new ReferenceConverter<Animal>(animals)); 
    List<User> users = data["users"].ToObject<List<User>>(serializer); 
    

전체 데모 : https://dotnetfiddle.net/uUuy7v

+0

답변 해 주시고 데모를 해주셔서 감사합니다! 나는 "자동적으로"사용하는 것이 나쁜 단어라고 생각한다. 내가 deserialize하는 클래스의 거대한 다양성이 있기 때문에, 나는 배열 구조'[ "FOO", "..."]'와 같은 발생을 처리 할 수있는 "일반적인"또는 불가지론적인 해결책을 기대하고있다. C# 형식을 개별적으로 코딩해야합니다. 나의 예에서는 '수의사'와 '동물'만 있지만 실제로는 훨씬 더 많습니다. 또한 (vet_id로 2 단계에서했던 것처럼) 속성을 검사하여 참조 된 객체의 유형을 결정할 수 있다고 보장 할 수 없습니다. –

+0

'BAR' 사전의 내용으로 무언가를 채우는 개념이 나아갈 길이라고 생각합니다. 'JsonConverter'가 프로퍼티에 대해 호출되고 프로퍼티 타입이 알려질 때까지 각 참조 된 _value_의 비 직렬화를 연기해야합니다. 그러나 내가 알 수있는 한,'JsonConverter'는 JSON에서 배열 구조체 [[ "FOO", "..."]가 존재하는 것이 아니라, 역 직렬화되고있는 알려진 유형에 의해 트리거됩니다. –