反序列化具有动态数量对象和键的 JSON

huangapple go评论83阅读模式
英文:

Deserialize json with dynamic number of objects and keys

问题

我有一个JSON字符串,我需要反序列化它(在一个WPF应用程序中,使用System.Net.Json)。树中的一个子对象(节点)包含可变数量和可变名称的属性/节点。
一个“最小”示例看起来像这样:

"intervals": [{
    "timestamp": 1677477728,
    "123456": {
        "subintervals": [{
            "max": "56.7",
            "label": "Leq"
        }, {
            "max": "58.1",
            "label": "Lmax"
        }]
    }
}, {
    "timestamp": 1677477730,
    "54321": {
        "subintervals": [{
            "value": "58.5",
            "label": "Leq"
        }, {
            "value": "59.5",
            "label": "Lmax"
        }]
    },
    "56789": {
        "subintervals": [{
            "value": "78.2",
            "label": "Lmax"
        }, {
            "value": "74.3",
            "label": "Leq"
        }]
    }
}]

即一个"intervals"数组,其中有一个"timestamp"和可变数量的具有可变名称的对象/节点。

下面是您提供的类的C#代码:

public class IntervalMeta
{
    public long? Timestamp { get; set; }
    [JsonExtensionData]
    public Dictionary<string, JsonElement>? MeasurementPoints { get; set; } = new();
}

您希望创建一个自定义的JsonConverter,用于将MeasurementPoints的值从JsonElement转换为更具体的类型。以下是您尝试的JsonConverter的代码:

public class MeasurementPointDictionaryJsonConverter : JsonConverter<Dictionary<string, SubInterval[]>>
{
    public override Dictionary<string, SubInterval[]>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var dict = new Dictionary<string, SubInterval[]>();
        // Issue 3: How do loop over the contents in the Interval node (containing 0 or more SubIntervals)
        while (reader.Read())
        {
            // Issue 1: How do I read the key?
            var measurementPointId = reader.GetString();
            // Issue 2: How do I deserialize the node to a SubInterval?
            string value = reader.GetString();
            var intervals = JsonSerializer.Deserialize<SubInterval[]>(value, options);
                    
            dict.Add(measurementPointId ?? "", intervals ?? new SubInterval[0]);
        }
        return dict;
    }

    public override void Write(Utf8JsonWriter writer, Dictionary<string, SubInterval[]> value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

要使JsonConverter起作用,您可以在MeasurementPoints属性上使用JsonConverter特性,如下所示:

[JsonConverter(typeof(MeasurementPointDictionaryJsonConverter))]
public Dictionary<string, SubInterval[]>? MeasurementPoints { get; set; } = new Dictionary<string, SubInterval[]>();

现在,MeasurementPoints应该能够使用MeasurementPointDictionaryJsonConverter正确地反序列化为Dictionary<string, SubInterval[]>。

英文:

I have a json string that I need to deserialize (in a WPF app, using System.Net.Json). A subobject (node) in the tree contains a variable number of propertiers/nodes with variable names.
A "minimal" sample looks like this:

> "intervals": [{
> "timestamp": 1677477728,
> "123456": {
> "subintervals": [{
> "max": "56.7",
> "label": "Leq"
> }, {
> "max": "58.1",
> "label": "Lmax"
> }
> ]
> }
> }, {
> "timestamp": 1677477730,
> "54321": {
> "subintervals": [{
> "value": "58.5",
> "label": "Leq"
> }, {
> "value": "59.5",
> "label": "Lmax"
> }
> ]
> },
> "56789": {
> "subintervals": [{
> "value": "78.2",
> "label": "Lmax"
> }, {
> "value": "74.3",
> "label": "Leq"
> }
> ]
> }
> } ]

I.e. a an array "intervals" with a "timestamp" and a varying number of objects/nodes that has a number-string as key.

With the following

public class IntervalMeta
    {
        public long? Timestamp { get; set; }
		[JsonExtensionData]
        public Dictionary&lt;string, JsonElement&gt;? MeasurementPoints { get; set; } = new();
    }

that gives a dictionary with JsonElements that I can step through and look for the desired properties. It would though be nice with a

public Dictionary&lt;string, SubInterval[]&gt;? MeasurementPoints { get; set; } = new();

where SubInterval :

public class SubInterval
    {
		public string? Max { get; set; }
        public string? Label { get; set; }
    }

I am hoping and trying to make a custom JsonConverter that I could use but there are a number of issues that I don't know how to handle:
Issue 0: how do I get it to use the custom JsonConverter?

My try for a converter:

public class MeasurementPointDictionaryJsonConverter : JsonConverter&lt;Dictionary&lt;string, SubInterval&gt;&gt;
    {
        public override Dictionary&lt;string, SubInterval[]&gt;? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var dict = new Dictionary&lt;string, SubInterval[]&gt;();
			// Issue 3: How do loop over the contents in the Interval node (containing 0 or more SubIntervals)
            while (reader.Read())
            {
                // Issue 1: How do I read the key?
                var measurementPointId = reader.GetString();
                // Issue 2: How do I deserialize the node to a SubInterval?
                string value = reader.GetString();
                var intervals = JsonSerializer.Deserialize&lt;SubInterval[]&gt;(value, options);
                
                dict.Add(measurementPointId ?? &quot;&quot;, intervals ?? new());
            }
            return dict;
        }

        public override void Write(Utf8JsonWriter writer, Dictionary&lt;string, SubInterval[]&gt; value, JsonSerializerOptions options)
        {
            throw new NotImplementedException();
        }
    }

I have tried just decorating the MeasurementPoints with:

[JsonConverter(typeof(MeasurementPointDictionaryJsonConverter))]
public Dictionary&lt;string, SubInterval[]&gt;? MeasurementPoints { get; set; } = new();

but that just result in MeasurementPoints == null. Adding the JsonExtensionData attribute:

[JsonExtensionData]
[JsonConverter(typeof(MeasurementPointDictionaryJsonConverter))]
public Dictionary&lt;string, SubInterval[]&gt;? MeasurementPoints { get; set; } = new();

Throws an error when trying to deserialize (seems that the JsonExtensionData can only handle Dictionary<string, JsonElement> or Dictionary<string,object>).

答案1

得分: 1

我提出了这样的东西。

但在此之前,我想指出,在您的字典中,"SubInterval" 不是单数,而是 "SubInterval" 的集合。如果我错了,请纠正我。

转换器:

public class CustomConverter : JsonConverter<List<IntervalMeta>>
{
    public override List<IntervalMeta> Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        // ...(代码省略)
    }

    public override void Write(Utf8JsonWriter writer, List<IntervalMeta> value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

测试中使用的其余代码:

public class IntervalMeta
{
    // ...(代码省略)
}

public class SubInterval
{
    // ...(代码省略)
}

class Program
{
    static async Task Main(string[] args)
    {
        var input = @"{...}"; //(代码省略)

        var test = JsonSerializer.Deserialize<List<IntervalMeta>>(input, new JsonSerializerOptions
        {
            Converters = { new CustomConverter() },
            PropertyNameCaseInsensitive = true
        });
    }
}

编辑:

我首先使用 Newtonsoft 解决了问题,因为我更熟悉它,然后转换为 System.Text.Json。但我看到 Serge 在 Newtonsoft 中添加了答案。然而,我使用了 CustomCreationConverter 来解决,所以我认为值得分享。

var list = JsonConvert.DeserializeObject<List<IntervalMeta>>(input, new CustomConverter());

public class CustomConverter : CustomCreationConverter<List<IntervalMeta>>
{
    public override List<IntervalMeta> Create(Type objectType)
    {
        return new List<IntervalMeta>();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        JObject jObject = JObject.Load(reader);

        IList<JToken> intervals = jObject["intervals"].Children().ToList();

        return intervals.Select(jToken =>
        {
            var item = new IntervalMeta();

            foreach (var jProperty in jToken.OfType<JProperty>())
            {
                if (jProperty.Name == "timestamp")
                {
                    if (long.TryParse(jProperty.Value.ToString(), out var value))
                    {
                        item.Timestamp = value;
                    }

                    continue;
                }

                var key = jProperty.Name;

                var jPropertyValue = jProperty.Value.First.First;
                var subinternvalDictionary = jPropertyValue.ToObject<SubInterval[]>();
                item.MeasurementPoints.Add(key, subinternvalDictionary);
            }

            return item;
        }).ToList();
    }
}
英文:

I come up with something like this.

But before that I want to note that in your dictionary you don't have singular SubInterval but collection of SubInterval. Correct me if I'm wrong.

Converter:

 public class CustomConverter : JsonConverter&lt;List&lt;IntervalMeta&gt;&gt;
    {
        public override List&lt;IntervalMeta&gt; Read(ref Utf8JsonReader reader, Type typeToConvert,
            JsonSerializerOptions options)
        {
            using var jsonDocument = JsonDocument.ParseValue(ref reader);
            var root = jsonDocument.RootElement;

            var intervals = root.GetProperty(&quot;intervals&quot;).EnumerateArray();

            var values = new List&lt;IntervalMeta&gt;();

            foreach (var interval in intervals)
            {
                var item = new IntervalMeta();

                    
                foreach (var property in interval.EnumerateObject())
                {
                    if (property.Name == &quot;timestamp&quot;)
                    {
                        if (property.Value.TryGetInt64(out var value))
                        {
                            item.Timestamp = value;
                        }
                    }
                    else
                    {
                        var key = property.Name;


                        var enumerateObject = property.Value.EnumerateObject();
                        if (!enumerateObject.Any()) continue;

                        var subIntervalArray = enumerateObject.First();

                        var subIntervalsJson = subIntervalArray.Value.GetRawText();

                        var subIntervals = JsonSerializer.Deserialize&lt;SubInterval[]&gt;(subIntervalsJson,
                            new JsonSerializerOptions()
                            {
                                PropertyNameCaseInsensitive = true
                            });

                        item.MeasurementPoints.Add(key, subIntervals);
                    }
                }

                values.Add(item);
            }

            return values;
        }

        public override void Write(Utf8JsonWriter writer, List&lt;IntervalMeta&gt; value, JsonSerializerOptions options)
        {
            throw new NotImplementedException();
        }
    }

Rest of code that I used for tests:

public class IntervalMeta
{
    public long? Timestamp { get; set; }

    public Dictionary&lt;string, SubInterval[]&gt; MeasurementPoints { get; set; } = new();
}

public class SubInterval
{
    public string Max { get; set; }
    public string Label { get; set; }
}

class Program
{
    static async Task Main(string[] args)
    {
        var input = @&quot;
        {
           &quot;&quot;intervals&quot;&quot;:[
              {
                 &quot;&quot;timestamp&quot;&quot;:1677477728,
                 &quot;&quot;123456&quot;&quot;:{
                    &quot;&quot;subintervals&quot;&quot;:[
                       {
                          &quot;&quot;max&quot;&quot;:&quot;&quot;56.7&quot;&quot;,
                          &quot;&quot;label&quot;&quot;:&quot;&quot;Leq&quot;&quot;
                       },
                       {
                          &quot;&quot;max&quot;&quot;:&quot;&quot;58.1&quot;&quot;,
                          &quot;&quot;label&quot;&quot;:&quot;&quot;Lmax&quot;&quot;
                       }
                    ]
                 }
              },
              {
                 &quot;&quot;timestamp&quot;&quot;:1677477730,
                 &quot;&quot;54321&quot;&quot;:{
                    &quot;&quot;subintervals&quot;&quot;:[
                       {
                          &quot;&quot;value&quot;&quot;:&quot;&quot;58.5&quot;&quot;,
                          &quot;&quot;label&quot;&quot;:&quot;&quot;Leq&quot;&quot;
                       },
                       {
                          &quot;&quot;value&quot;&quot;:&quot;&quot;59.5&quot;&quot;,
                          &quot;&quot;label&quot;&quot;:&quot;&quot;Lmax&quot;&quot;
                       }
                    ]
                 },
                 &quot;&quot;56789&quot;&quot;:{
                    &quot;&quot;subintervals&quot;&quot;:[
                       {
                          &quot;&quot;value&quot;&quot;:&quot;&quot;78.2&quot;&quot;,
                          &quot;&quot;label&quot;&quot;:&quot;&quot;Lmax&quot;&quot;
                       },
                       {
                          &quot;&quot;value&quot;&quot;:&quot;&quot;74.3&quot;&quot;,
                          &quot;&quot;label&quot;&quot;:&quot;&quot;Leq&quot;&quot;
                       }
                    ]
                 }
              }
           ]
        }&quot;;

        var test = JsonSerializer.Deserialize&lt;List&lt;IntervalMeta&gt;&gt;(input, new JsonSerializerOptions
        {
            Converters = { new CustomConverter() },
            PropertyNameCaseInsensitive = true
        });

    }

EDIT:

I firstly solved using Newtonsoft because I more familiar with and then converted to System.Text.Json. But I see that Serge added answer in Newtonsoft. However I solved a little diffrent using CustomCreationConverter, so I think it is worth sharing.

var list = JsonConvert.DeserializeObject&lt;List&lt;IntervalMeta&gt;&gt;(input, new CustomConverter());

public class CustomConverter : CustomCreationConverter&lt;List&lt;IntervalMeta&gt;&gt;
{
    public override List&lt;IntervalMeta&gt; Create(Type objectType)
    {
        return new List&lt;IntervalMeta&gt;();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        JObject jObject = JObject.Load(reader);

        IList&lt;JToken&gt; intervals = jObject[&quot;intervals&quot;].Children().ToList();

        return intervals.Select(jToken =&gt;
        {
            var item = new IntervalMeta();

            foreach (var jProperty in jToken.OfType&lt;JProperty&gt;())
            {
                if (jProperty.Name == &quot;timestamp&quot;)
                {
                    if (long.TryParse(jProperty.Value.ToString(), out var value))
                    {
                        item.Timestamp = value;
                    }

                    continue;
                }

                var key = jProperty.Name;

                var jPropertyValue = jProperty.Value.First.First;
                var subinternvalDictionary = jPropertyValue.ToObject&lt;SubInterval[]&gt;();
                item.MeasurementPoints.Add(key, subinternvalDictionary);
            }


            return item;
        }).ToList();
    }
}

答案2

得分: 1

你可以尝试这个转换器:

using Newtonsoft.Json;

List<Interval> intervals = JsonConvert.DeserializeObject<List<Interval>>(json, new DictToListConverter());

public class DictToListConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jObj = JObject.Load(reader);

        return jObj["intervals"].Select(x => GetInterval((JObject)x)).ToList();
    }

    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public Interval GetInterval(JObject jObj)
    {
        var interval = jObj.ToObject<IntervalTemp>();
        interval.subintervals = new List<SubintervalItem>();

        foreach (var item in interval.dict)
        {
            var sii = new SubintervalItem { name = item.Key, subintervals = new List<Subinterval>() };
            sii.subintervals = item.Value["subintervals"].Select(d => d.ToObject<Subinterval>()).ToList();
            interval.subintervals.Add(sii);
        }

        return JObject.FromObject(interval).ToObject<Interval>();

        // 或者如果性能是一个问题
        // interval.dict = null;
        // return interval;
    }
}

类:

public class IntervalTemp : Interval
{
    [JsonExtensionData]
    public Dictionary<string, JToken> dict { get; set; }
}

public class Interval
{
    public long timestamp { get; set; }
    public List<SubintervalItem> subintervals { get; set; }
}

public class SubintervalItem
{
    public string name { get; set; }
    public List<Subinterval> subintervals { get; set; }
}

public class Subinterval
{
    public string max { get; set; }
    public string label { get; set; }
    public string value { get; set; }
}
英文:

you can try this converter

using Newtonsoft.Json

List&lt;Interval&gt; intervals=JsonConvert.DeserializeObject&lt;List&lt;Interval&gt;&gt;(json,new DictToListConverter());

public class DictToListConverter : JsonConverter
{
	public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
	{
		throw new NotImplementedException();
	}

	public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
	{
		var jObj = JObject.Load(reader);
		
		return  jObj[&quot;intervals&quot;].Select(x =&gt; GetInterval((JObject)x)).ToList();
                               
	}

	public override bool CanConvert(Type objectType)
	{
		return true;
	}
	public  Interval GetInterval(JObject jObj)
	{
		var interval = jObj.ToObject&lt;IntervalTemp&gt;();
		interval.subintervals = new List&lt;SubintervalItem&gt;();

		foreach (var item in interval.dict)
		{
			var sii = new SubintervalItem { name = item.Key, subintervals = new List&lt;Subinterval&gt;() };
			sii.subintervals = item.Value[&quot;subintervals&quot;].Select(d =&gt; d.ToObject&lt;Subinterval&gt;()).ToList();
			interval.subintervals.Add(sii);
		}

		return JObject.FromObject(interval).ToObject&lt;Interval&gt;();

        //or if a performance is an issue
		//interval.dict = null;
		//return interval;
	}
}

classes

public class IntervalTemp : Interval
{
	[JsonExtensionData]
	public Dictionary&lt;string, JToken&gt; dict { get; set; }
	
}
public class Interval
{
	public long timestamp { get; set; }
	public List&lt;SubintervalItem&gt; subintervals { get; set; }
}
public class SubintervalItem
{
	public string name { get; set; }
	public List&lt;Subinterval&gt; subintervals { get; set; }
}

public class Subinterval
{
	public string max { get; set; }
	public string label { get; set; }
	public string value { get; set; }
}

huangapple
  • 本文由 发表于 2023年3月7日 19:18:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/75661312.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定