skip to Main Content

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>).

Any help would be most appreciated (I have asked chatgtp but I didn’t give me much more 🙂 ).

EDIT: Corrected SubInterval => SubIntervals[]. Kudos to @KJanec

2

Answers


  1. 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();
        }
    }
    
    Login or Signup to reply.
  2. 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; }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search