programing

알려진 필드와 알 수없는 필드로 json 역 직렬화

nicescript 2021. 1. 15. 07:53
반응형

알려진 필드와 알 수없는 필드로 json 역 직렬화


다음과 같은 json 결과가 주어집니다. 기본 json 결과에는 알려진 필드 세트가 있습니다.

{
    "id": "7908",
    "name": "product name"
}

그러나 결과를 요청할 때 이름을 알 수없는 추가 필드 (이 예에서는 _unknown_field_name_1_unknown_field_name_2) 로 확장 할 수 있습니다 .

{
    "id": "7908",
    "name": "product name",
    "_unknown_field_name_1": "some value",
    "_unknown_field_name_2": "some value"
}

json 결과를 알려진 필드에 대한 속성이있는 클래스와 직렬화 및 역 직렬화하고 알 수없는 필드 (속성이없는)를 사전과 같은 속성 (또는 여러 속성)에 매핑하여 액세스 및 수정.

public class Product
{
    public string id { get; set; }
    public string name { get; set; }
    public Dictionary<string, string> fields { get; set; }
}

json serializer에 연결하고 누락 된 구성원에 대한 매핑을 직접 수행하는 방법이 필요하다고 생각합니다 (직렬화 및 역 직렬화 모두). 저는 다양한 가능성을 살펴 보았습니다.

  • json.net 및 사용자 지정 계약 해결 프로그램 (방법을 알 수 없음)
  • datacontract serializer (onserialized, onserializing 만 재정의 할 수 있음)
  • 동적으로 직렬화하고 사용자 지정 매핑을 수행합니다 (이것은 작동 할 수 있지만 많은 작업으로 보입니다).
  • DynamicObject에서 제품 상속 (시리얼 라이저는 리플렉션과 함께 작동하며 trygetmember 및 trysetmember 메서드를 호출하지 않음)

restsharp를 사용하고 있지만 모든 직렬 변환기를 연결할 수 있습니다.

아, 그리고 나는 json 결과를 변경할 수 없으며 이것 또는 이것도 나에게 도움 되지 않았습니다.

업데이트 : 이 보이는 더 좋아 : http://geekswithblogs.net/DavidHoerster/archive/2011/07/26/json.net-custom-convertersndasha-quick-tour.aspx


이 문제를 해결하는 더 쉬운 옵션 은 JSON .NET JsonExtensionDataAttribute 를 사용하는 입니다.

public class MyClass
{
   // known field
   public decimal TaxRate { get; set; }

   // extra fields
   [JsonExtensionData]
   private IDictionary<string, JToken> _extraStuff;
}

여기 프로젝트 블로그에이 샘플이 있습니다.

업데이트 여기 에는 JSON .NET v5 릴리스 5 이상이 필요합니다.


참조 https://gist.github.com/LodewijkSioen/5101814를

당신이 찾고 있던 것은 관습이었습니다 JsonConverter


내가 그렇게별로 좋아하지 않지만 이것은 당신이 그것을 해결할 수있는 방법입니다. Newton / JSON.Net을 사용하여 해결했습니다. 역 직렬화에도 JsonConverter를 사용할 수 있다고 가정합니다.

private const string Json = "{\"id\":7908,\"name\":\"product name\",\"_unknown_field_name_1\":\"some value\",\"_unknown_field_name_2\":\"some value\"}";

    [TestMethod]
    public void TestDeserializeUnknownMembers()
    {
        var @object = JObject.Parse(Json);

        var serializer = new Newtonsoft.Json.JsonSerializer();
        serializer.MissingMemberHandling = Newtonsoft.Json.MissingMemberHandling.Error;
        serializer.Error += (sender, eventArgs) =>
            {
                var contract = eventArgs.CurrentObject as Contract ?? new Contract();
                contract.UnknownValues.Add(eventArgs.ErrorContext.Member.ToString(), @object[eventArgs.ErrorContext.Member.ToString()].Value<string>());
                eventArgs.ErrorContext.Handled = true;
            };

        using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(Json)))
        using (StreamReader streamReader = new StreamReader(memoryStream))
        using (JsonReader jsonReader = new JsonTextReader(streamReader))
        {
            var result = serializer.Deserialize<Contract>(jsonReader);
            Assert.IsTrue(result.UnknownValues.ContainsKey("_unknown_field_name_1"));
            Assert.IsTrue(result.UnknownValues.ContainsKey("_unknown_field_name_2"));
        }
    }

    [TestMethod]
    public void TestSerializeUnknownMembers()
    {
        var deserializedObject = new Contract
        {
            id = 7908,
            name = "product name",
            UnknownValues = new Dictionary<string, string>
        {
            {"_unknown_field_name_1", "some value"},
            {"_unknown_field_name_2", "some value"}
        }
        };

        var json = JsonConvert.SerializeObject(deserializedObject, new DictionaryConverter());
        Console.WriteLine(Json);
        Console.WriteLine(json);
        Assert.AreEqual(Json, json);
    }
}

class DictionaryConverter : JsonConverter
{
    public DictionaryConverter()
    {

    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Contract);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var contract = value as Contract;
        var json = JsonConvert.SerializeObject(value);
        var dictArray = String.Join(",", contract.UnknownValues.Select(pair => "\"" + pair.Key + "\":\"" + pair.Value + "\""));

        json = json.Substring(0, json.Length - 1) + "," + dictArray + "}";
        writer.WriteRaw(json);
    }
}

class Contract
{
    public Contract()
    {
        UnknownValues = new Dictionary<string, string>();
    }

    public int id { get; set; }
    public string name { get; set; }

    [JsonIgnore]
    public Dictionary<string, string> UnknownValues { get; set; }
}
}

최근에 비슷한 문제가 생겼기 때문에 반지에 모자를 던질 것이라고 생각했습니다. 다음은 역 직렬화하려는 JSON의 예입니다.

{
    "agencyId": "agency1",
    "overrides": {
        "assumption.discount.rates": "value: 0.07",
        ".plan": {
            "plan1": {
                "assumption.payroll.growth": "value: 0.03",
                "provision.eeContrib.rate": "value: 0.35"
            },
            "plan2": {
                ".classAndTier": {
                    "misc:tier1": {
                        "provision.eeContrib.rate": "value: 0.4"
                    },
                    "misc:tier2": {
                        "provision.eeContrib.rate": "value: 0.375"
                    }
                }
            }
        }
    }
}

이것은 재정의가 다른 수준에서 적용되고 트리 아래로 상속되는 시스템을위한 것입니다. 어쨌든 내가 원했던 데이터 모델은 이러한 특별한 상속 규칙이 제공되는 속성 백을 가질 수있게 해주는 것이 었습니다.

내가 끝내는 것은 다음과 같습니다.

public class TestDataModel
{
    public string AgencyId;
    public int Years;
    public PropertyBagModel Overrides;
}

public class ParticipantFilterModel
{
    public string[] ClassAndTier;
    public string[] BargainingUnit;
    public string[] Department;
}

public class PropertyBagModel
{
    [JsonExtensionData]
    private readonly Dictionary<string, JToken> _extensionData = new Dictionary<string, JToken>();

    [JsonIgnore]
    public readonly Dictionary<string, string> Values = new Dictionary<string, string>();

    [JsonProperty(".plan", NullValueHandling = NullValueHandling.Ignore)]
    public Dictionary<string, PropertyBagModel> ByPlan;

    [JsonProperty(".classAndTier", NullValueHandling = NullValueHandling.Ignore)]
    public Dictionary<string, PropertyBagModel> ByClassAndTier;

    [JsonProperty(".bargainingUnit", NullValueHandling = NullValueHandling.Ignore)]
    public Dictionary<string, PropertyBagModel> ByBarginingUnit;

    [OnSerializing]
    private void OnSerializing(StreamingContext context)
    {
        foreach (var kvp in Values)
            _extensionData.Add(kvp.Key, kvp.Value);
    }

    [OnSerialized]
    private void OnSerialized(StreamingContext context)
    {
        _extensionData.Clear();
    }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        Values.Clear();
        foreach (var kvp in _extensionData.Where(x => x.Value.Type == JTokenType.String))
            Values.Add(kvp.Key, kvp.Value.Value<string>());
        _extensionData.Clear();
    }
}

기본 아이디어는 다음과 같습니다.

  1. JSON.NET에 의한 역 직렬화의 PropertyBagModel에는 ByPlan, ByClassAndTier 등의 필드가 채워져 있고 private _extensionData 필드도 채워져 있습니다.
  2. 그런 다음 JSON.NET은 private OnDeserialized () 메서드를 호출하고 데이터를 _extensionData에서 Values로 적절하게 이동합니다 (또는 그렇지 않으면 바닥에 떨어 뜨립니다-아마도 알고 싶은 것이 있다면이를 기록 할 수있을 것입니다). 그런 다음 _extensionData에서 추가 건크를 제거하여 메모리를 소비하지 않습니다.
  3. 직렬화에서 OnSerializing 메서드는 항목을 _extensionData로 이동하여 저장하는 호출을받습니다.
  4. 직렬화가 완료되면 OnSerialized가 호출되고 _extensionData에서 추가 항목을 제거합니다.

We could further delete and recreate the _extensionData Dictionary when needed but I didn't see a real value in this as I'm not using tons of these objects. To do this we'd just create on OnSerializing and delete on OnSerialized. On OnDeserializing, instead of clearing, we could free it.


I was looking into a similar issue and found this post.

Here is a way to do it using reflection.

To make it more generic, one should check the type of the property instead of simply using ToString() in propertyInfo.SetValue, unless OFC all the actual properties are strings.

Also, lowercase property names is not standard in C# but given that GetProperty is case sensitive there are few other options.

public class Product
{
    private Type _type;

    public Product()
    {
        fields = new Dictionary<string, object>();
        _type = GetType();
    }

    public string id { get; set; }
    public string name { get; set; }
    public Dictionary<string, object> fields { get; set; }

    public void SetProperty(string key, object value)
    {
        var propertyInfo = _type.GetProperty(key);
        if (null == propertyInfo)
        {
            fields.Add(key,value);
            return;
        }
        propertyInfo.SetValue(this, value.ToString());
    }
}
...
private const string JsonTest = "{\"id\":7908,\"name\":\"product name\",\"_unknown_field_name_1\":\"some value\",\"_unknown_field_name_2\":\"some value\"}";

var product = new Product();
var data = JObject.Parse(JsonTest);
foreach (var item in data)
{
    product.SetProperty(item.Key, item.Value);
}

ReferenceURL : https://stackoverflow.com/questions/15253875/deserialize-json-with-known-and-unknown-fields

반응형