0

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

TylerH
  • 20,799
  • 66
  • 75
  • 101
Erik Thysell
  • 1,160
  • 1
  • 14
  • 31

2 Answers2

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();
    }
}
KJanek
  • 147
  • 1
  • 11
1

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; }
}
Serge
  • 40,935
  • 4
  • 18
  • 45