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

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

Deserialize json with dynamic number of objects and keys

问题

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

  1. "intervals": [{
  2. "timestamp": 1677477728,
  3. "123456": {
  4. "subintervals": [{
  5. "max": "56.7",
  6. "label": "Leq"
  7. }, {
  8. "max": "58.1",
  9. "label": "Lmax"
  10. }]
  11. }
  12. }, {
  13. "timestamp": 1677477730,
  14. "54321": {
  15. "subintervals": [{
  16. "value": "58.5",
  17. "label": "Leq"
  18. }, {
  19. "value": "59.5",
  20. "label": "Lmax"
  21. }]
  22. },
  23. "56789": {
  24. "subintervals": [{
  25. "value": "78.2",
  26. "label": "Lmax"
  27. }, {
  28. "value": "74.3",
  29. "label": "Leq"
  30. }]
  31. }
  32. }]

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

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

  1. public class IntervalMeta
  2. {
  3. public long? Timestamp { get; set; }
  4. [JsonExtensionData]
  5. public Dictionary<string, JsonElement>? MeasurementPoints { get; set; } = new();
  6. }

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

  1. public class MeasurementPointDictionaryJsonConverter : JsonConverter<Dictionary<string, SubInterval[]>>
  2. {
  3. public override Dictionary<string, SubInterval[]>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  4. {
  5. var dict = new Dictionary<string, SubInterval[]>();
  6. // Issue 3: How do loop over the contents in the Interval node (containing 0 or more SubIntervals)
  7. while (reader.Read())
  8. {
  9. // Issue 1: How do I read the key?
  10. var measurementPointId = reader.GetString();
  11. // Issue 2: How do I deserialize the node to a SubInterval?
  12. string value = reader.GetString();
  13. var intervals = JsonSerializer.Deserialize<SubInterval[]>(value, options);
  14. dict.Add(measurementPointId ?? "", intervals ?? new SubInterval[0]);
  15. }
  16. return dict;
  17. }
  18. public override void Write(Utf8JsonWriter writer, Dictionary<string, SubInterval[]> value, JsonSerializerOptions options)
  19. {
  20. throw new NotImplementedException();
  21. }
  22. }

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

  1. [JsonConverter(typeof(MeasurementPointDictionaryJsonConverter))]
  2. 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

  1. public class IntervalMeta
  2. {
  3. public long? Timestamp { get; set; }
  4. [JsonExtensionData]
  5. public Dictionary&lt;string, JsonElement&gt;? MeasurementPoints { get; set; } = new();
  6. }

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

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

where SubInterval :

  1. public class SubInterval
  2. {
  3. public string? Max { get; set; }
  4. public string? Label { get; set; }
  5. }

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:

  1. public class MeasurementPointDictionaryJsonConverter : JsonConverter&lt;Dictionary&lt;string, SubInterval&gt;&gt;
  2. {
  3. public override Dictionary&lt;string, SubInterval[]&gt;? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
  4. {
  5. var dict = new Dictionary&lt;string, SubInterval[]&gt;();
  6. // Issue 3: How do loop over the contents in the Interval node (containing 0 or more SubIntervals)
  7. while (reader.Read())
  8. {
  9. // Issue 1: How do I read the key?
  10. var measurementPointId = reader.GetString();
  11. // Issue 2: How do I deserialize the node to a SubInterval?
  12. string value = reader.GetString();
  13. var intervals = JsonSerializer.Deserialize&lt;SubInterval[]&gt;(value, options);
  14. dict.Add(measurementPointId ?? &quot;&quot;, intervals ?? new());
  15. }
  16. return dict;
  17. }
  18. public override void Write(Utf8JsonWriter writer, Dictionary&lt;string, SubInterval[]&gt; value, JsonSerializerOptions options)
  19. {
  20. throw new NotImplementedException();
  21. }
  22. }

I have tried just decorating the MeasurementPoints with:

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

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

  1. [JsonExtensionData]
  2. [JsonConverter(typeof(MeasurementPointDictionaryJsonConverter))]
  3. 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" 的集合。如果我错了,请纠正我。

转换器:

  1. public class CustomConverter : JsonConverter<List<IntervalMeta>>
  2. {
  3. public override List<IntervalMeta> Read(ref Utf8JsonReader reader, Type typeToConvert,
  4. JsonSerializerOptions options)
  5. {
  6. // ...(代码省略)
  7. }
  8. public override void Write(Utf8JsonWriter writer, List<IntervalMeta> value, JsonSerializerOptions options)
  9. {
  10. throw new NotImplementedException();
  11. }
  12. }

测试中使用的其余代码:

  1. public class IntervalMeta
  2. {
  3. // ...(代码省略)
  4. }
  5. public class SubInterval
  6. {
  7. // ...(代码省略)
  8. }
  9. class Program
  10. {
  11. static async Task Main(string[] args)
  12. {
  13. var input = @"{...}"; //(代码省略)
  14. var test = JsonSerializer.Deserialize<List<IntervalMeta>>(input, new JsonSerializerOptions
  15. {
  16. Converters = { new CustomConverter() },
  17. PropertyNameCaseInsensitive = true
  18. });
  19. }
  20. }

编辑:

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

  1. var list = JsonConvert.DeserializeObject<List<IntervalMeta>>(input, new CustomConverter());
  2. public class CustomConverter : CustomCreationConverter<List<IntervalMeta>>
  3. {
  4. public override List<IntervalMeta> Create(Type objectType)
  5. {
  6. return new List<IntervalMeta>();
  7. }
  8. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
  9. {
  10. JObject jObject = JObject.Load(reader);
  11. IList<JToken> intervals = jObject["intervals"].Children().ToList();
  12. return intervals.Select(jToken =>
  13. {
  14. var item = new IntervalMeta();
  15. foreach (var jProperty in jToken.OfType<JProperty>())
  16. {
  17. if (jProperty.Name == "timestamp")
  18. {
  19. if (long.TryParse(jProperty.Value.ToString(), out var value))
  20. {
  21. item.Timestamp = value;
  22. }
  23. continue;
  24. }
  25. var key = jProperty.Name;
  26. var jPropertyValue = jProperty.Value.First.First;
  27. var subinternvalDictionary = jPropertyValue.ToObject<SubInterval[]>();
  28. item.MeasurementPoints.Add(key, subinternvalDictionary);
  29. }
  30. return item;
  31. }).ToList();
  32. }
  33. }
英文:

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:

  1. public class CustomConverter : JsonConverter&lt;List&lt;IntervalMeta&gt;&gt;
  2. {
  3. public override List&lt;IntervalMeta&gt; Read(ref Utf8JsonReader reader, Type typeToConvert,
  4. JsonSerializerOptions options)
  5. {
  6. using var jsonDocument = JsonDocument.ParseValue(ref reader);
  7. var root = jsonDocument.RootElement;
  8. var intervals = root.GetProperty(&quot;intervals&quot;).EnumerateArray();
  9. var values = new List&lt;IntervalMeta&gt;();
  10. foreach (var interval in intervals)
  11. {
  12. var item = new IntervalMeta();
  13. foreach (var property in interval.EnumerateObject())
  14. {
  15. if (property.Name == &quot;timestamp&quot;)
  16. {
  17. if (property.Value.TryGetInt64(out var value))
  18. {
  19. item.Timestamp = value;
  20. }
  21. }
  22. else
  23. {
  24. var key = property.Name;
  25. var enumerateObject = property.Value.EnumerateObject();
  26. if (!enumerateObject.Any()) continue;
  27. var subIntervalArray = enumerateObject.First();
  28. var subIntervalsJson = subIntervalArray.Value.GetRawText();
  29. var subIntervals = JsonSerializer.Deserialize&lt;SubInterval[]&gt;(subIntervalsJson,
  30. new JsonSerializerOptions()
  31. {
  32. PropertyNameCaseInsensitive = true
  33. });
  34. item.MeasurementPoints.Add(key, subIntervals);
  35. }
  36. }
  37. values.Add(item);
  38. }
  39. return values;
  40. }
  41. public override void Write(Utf8JsonWriter writer, List&lt;IntervalMeta&gt; value, JsonSerializerOptions options)
  42. {
  43. throw new NotImplementedException();
  44. }
  45. }

Rest of code that I used for tests:

  1. public class IntervalMeta
  2. {
  3. public long? Timestamp { get; set; }
  4. public Dictionary&lt;string, SubInterval[]&gt; MeasurementPoints { get; set; } = new();
  5. }
  6. public class SubInterval
  7. {
  8. public string Max { get; set; }
  9. public string Label { get; set; }
  10. }
  11. class Program
  12. {
  13. static async Task Main(string[] args)
  14. {
  15. var input = @&quot;
  16. {
  17. &quot;&quot;intervals&quot;&quot;:[
  18. {
  19. &quot;&quot;timestamp&quot;&quot;:1677477728,
  20. &quot;&quot;123456&quot;&quot;:{
  21. &quot;&quot;subintervals&quot;&quot;:[
  22. {
  23. &quot;&quot;max&quot;&quot;:&quot;&quot;56.7&quot;&quot;,
  24. &quot;&quot;label&quot;&quot;:&quot;&quot;Leq&quot;&quot;
  25. },
  26. {
  27. &quot;&quot;max&quot;&quot;:&quot;&quot;58.1&quot;&quot;,
  28. &quot;&quot;label&quot;&quot;:&quot;&quot;Lmax&quot;&quot;
  29. }
  30. ]
  31. }
  32. },
  33. {
  34. &quot;&quot;timestamp&quot;&quot;:1677477730,
  35. &quot;&quot;54321&quot;&quot;:{
  36. &quot;&quot;subintervals&quot;&quot;:[
  37. {
  38. &quot;&quot;value&quot;&quot;:&quot;&quot;58.5&quot;&quot;,
  39. &quot;&quot;label&quot;&quot;:&quot;&quot;Leq&quot;&quot;
  40. },
  41. {
  42. &quot;&quot;value&quot;&quot;:&quot;&quot;59.5&quot;&quot;,
  43. &quot;&quot;label&quot;&quot;:&quot;&quot;Lmax&quot;&quot;
  44. }
  45. ]
  46. },
  47. &quot;&quot;56789&quot;&quot;:{
  48. &quot;&quot;subintervals&quot;&quot;:[
  49. {
  50. &quot;&quot;value&quot;&quot;:&quot;&quot;78.2&quot;&quot;,
  51. &quot;&quot;label&quot;&quot;:&quot;&quot;Lmax&quot;&quot;
  52. },
  53. {
  54. &quot;&quot;value&quot;&quot;:&quot;&quot;74.3&quot;&quot;,
  55. &quot;&quot;label&quot;&quot;:&quot;&quot;Leq&quot;&quot;
  56. }
  57. ]
  58. }
  59. }
  60. ]
  61. }&quot;;
  62. var test = JsonSerializer.Deserialize&lt;List&lt;IntervalMeta&gt;&gt;(input, new JsonSerializerOptions
  63. {
  64. Converters = { new CustomConverter() },
  65. PropertyNameCaseInsensitive = true
  66. });
  67. }

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.

  1. var list = JsonConvert.DeserializeObject&lt;List&lt;IntervalMeta&gt;&gt;(input, new CustomConverter());
  2. public class CustomConverter : CustomCreationConverter&lt;List&lt;IntervalMeta&gt;&gt;
  3. {
  4. public override List&lt;IntervalMeta&gt; Create(Type objectType)
  5. {
  6. return new List&lt;IntervalMeta&gt;();
  7. }
  8. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
  9. {
  10. JObject jObject = JObject.Load(reader);
  11. IList&lt;JToken&gt; intervals = jObject[&quot;intervals&quot;].Children().ToList();
  12. return intervals.Select(jToken =&gt;
  13. {
  14. var item = new IntervalMeta();
  15. foreach (var jProperty in jToken.OfType&lt;JProperty&gt;())
  16. {
  17. if (jProperty.Name == &quot;timestamp&quot;)
  18. {
  19. if (long.TryParse(jProperty.Value.ToString(), out var value))
  20. {
  21. item.Timestamp = value;
  22. }
  23. continue;
  24. }
  25. var key = jProperty.Name;
  26. var jPropertyValue = jProperty.Value.First.First;
  27. var subinternvalDictionary = jPropertyValue.ToObject&lt;SubInterval[]&gt;();
  28. item.MeasurementPoints.Add(key, subinternvalDictionary);
  29. }
  30. return item;
  31. }).ToList();
  32. }
  33. }

答案2

得分: 1

你可以尝试这个转换器:

  1. using Newtonsoft.Json;
  2. List<Interval> intervals = JsonConvert.DeserializeObject<List<Interval>>(json, new DictToListConverter());
  3. public class DictToListConverter : JsonConverter
  4. {
  5. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  6. {
  7. throw new NotImplementedException();
  8. }
  9. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  10. {
  11. var jObj = JObject.Load(reader);
  12. return jObj["intervals"].Select(x => GetInterval((JObject)x)).ToList();
  13. }
  14. public override bool CanConvert(Type objectType)
  15. {
  16. return true;
  17. }
  18. public Interval GetInterval(JObject jObj)
  19. {
  20. var interval = jObj.ToObject<IntervalTemp>();
  21. interval.subintervals = new List<SubintervalItem>();
  22. foreach (var item in interval.dict)
  23. {
  24. var sii = new SubintervalItem { name = item.Key, subintervals = new List<Subinterval>() };
  25. sii.subintervals = item.Value["subintervals"].Select(d => d.ToObject<Subinterval>()).ToList();
  26. interval.subintervals.Add(sii);
  27. }
  28. return JObject.FromObject(interval).ToObject<Interval>();
  29. // 或者如果性能是一个问题
  30. // interval.dict = null;
  31. // return interval;
  32. }
  33. }

类:

  1. public class IntervalTemp : Interval
  2. {
  3. [JsonExtensionData]
  4. public Dictionary<string, JToken> dict { get; set; }
  5. }
  6. public class Interval
  7. {
  8. public long timestamp { get; set; }
  9. public List<SubintervalItem> subintervals { get; set; }
  10. }
  11. public class SubintervalItem
  12. {
  13. public string name { get; set; }
  14. public List<Subinterval> subintervals { get; set; }
  15. }
  16. public class Subinterval
  17. {
  18. public string max { get; set; }
  19. public string label { get; set; }
  20. public string value { get; set; }
  21. }
英文:

you can try this converter

  1. using Newtonsoft.Json
  2. List&lt;Interval&gt; intervals=JsonConvert.DeserializeObject&lt;List&lt;Interval&gt;&gt;(json,new DictToListConverter());
  3. public class DictToListConverter : JsonConverter
  4. {
  5. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  6. {
  7. throw new NotImplementedException();
  8. }
  9. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  10. {
  11. var jObj = JObject.Load(reader);
  12. return jObj[&quot;intervals&quot;].Select(x =&gt; GetInterval((JObject)x)).ToList();
  13. }
  14. public override bool CanConvert(Type objectType)
  15. {
  16. return true;
  17. }
  18. public Interval GetInterval(JObject jObj)
  19. {
  20. var interval = jObj.ToObject&lt;IntervalTemp&gt;();
  21. interval.subintervals = new List&lt;SubintervalItem&gt;();
  22. foreach (var item in interval.dict)
  23. {
  24. var sii = new SubintervalItem { name = item.Key, subintervals = new List&lt;Subinterval&gt;() };
  25. sii.subintervals = item.Value[&quot;subintervals&quot;].Select(d =&gt; d.ToObject&lt;Subinterval&gt;()).ToList();
  26. interval.subintervals.Add(sii);
  27. }
  28. return JObject.FromObject(interval).ToObject&lt;Interval&gt;();
  29. //or if a performance is an issue
  30. //interval.dict = null;
  31. //return interval;
  32. }
  33. }

classes

  1. public class IntervalTemp : Interval
  2. {
  3. [JsonExtensionData]
  4. public Dictionary&lt;string, JToken&gt; dict { get; set; }
  5. }
  6. public class Interval
  7. {
  8. public long timestamp { get; set; }
  9. public List&lt;SubintervalItem&gt; subintervals { get; set; }
  10. }
  11. public class SubintervalItem
  12. {
  13. public string name { get; set; }
  14. public List&lt;Subinterval&gt; subintervals { get; set; }
  15. }
  16. public class Subinterval
  17. {
  18. public string max { get; set; }
  19. public string label { get; set; }
  20. public string value { get; set; }
  21. }

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:

确定