英文:
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<string, JsonElement>? 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<string, SubInterval[]>? 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<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());
}
return dict;
}
public override void Write(Utf8JsonWriter writer, Dictionary<string, SubInterval[]> value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
I have tried just decorating the MeasurementPoints
with:
[JsonConverter(typeof(MeasurementPointDictionaryJsonConverter))]
public Dictionary<string, SubInterval[]>? MeasurementPoints { get; set; } = new();
but that just result in MeasurementPoints == null
. Adding the JsonExtensionData
attribute:
[JsonExtensionData]
[JsonConverter(typeof(MeasurementPointDictionaryJsonConverter))]
public Dictionary<string, SubInterval[]>? 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<List<IntervalMeta>>
{
public override List<IntervalMeta> Read(ref Utf8JsonReader reader, Type typeToConvert,
JsonSerializerOptions options)
{
using var jsonDocument = JsonDocument.ParseValue(ref reader);
var root = jsonDocument.RootElement;
var intervals = root.GetProperty("intervals").EnumerateArray();
var values = new List<IntervalMeta>();
foreach (var interval in intervals)
{
var item = new IntervalMeta();
foreach (var property in interval.EnumerateObject())
{
if (property.Name == "timestamp")
{
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<SubInterval[]>(subIntervalsJson,
new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
});
item.MeasurementPoints.Add(key, subIntervals);
}
}
values.Add(item);
}
return values;
}
public override void Write(Utf8JsonWriter writer, List<IntervalMeta> value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
Rest of code that I used for tests:
public class IntervalMeta
{
public long? Timestamp { get; set; }
public Dictionary<string, SubInterval[]> 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 = @"
{
""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""
}
]
}
}
]
}";
var test = JsonSerializer.Deserialize<List<IntervalMeta>>(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<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();
}
}
答案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<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>();
//or if a performance is an issue
//interval.dict = null;
//return interval;
}
}
classes
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; }
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论