Home > Enterprise >  Parse dynamic JSON data via JSON.NET
Parse dynamic JSON data via JSON.NET

Time:03-09

I have a set of, well, interestingly formed JSON that I need to read using C#, in my case specifically using JSON.NET although that could likely be done using System.Text.Json as well.

The data has a structure similar to this:

[
    {
        "Name": "Name A",
        "Value": "Apples"
    },
    {
        "Name": "Name B",
        "Value": {
            "key1": "value1",
            "key2": "value2"
        }
    }
]

In short, an array of objects of somewhat different shape, specifically the 'Value' property is varying. I am ONLY interested in records having Value data of key/value (Dictionary<string, string>) type, if that helps.

What is the best (simplest in this case) way to read this data into some kind of typed representation? I say typed as that is what I would like to work with but it is not strictly a requirement.

Something like "Hey JSON.NET, please give me all records where 'Value' is of type key/value, if possible as an object structure where Value is materialized as typed Dictionary<string, string> objects. Other records may be discarded if that helps. Thanks!".

It would be really great if this could somehow be implemented using POCO objects.

CodePudding user response:

try this

    var jsonParsed = JArray.Parse(json); 

    var items = new List<Item>();

    foreach (JObject item in jsonParsed)
    {
        var name = string.Empty;
        foreach (var prop in item.Properties())
        {
            if (prop.Value.Type.ToString() != "Object")
            {
                if (prop.Name == "Name") name = (string)prop.Value;
                continue;
            }
            items.Add(new Item
            {
                Name = name,
                Value = prop.Value.ToObject<Dictionary<string, string>>()
            });
        }
    }

result

[
  {
    "Name": "Name B",
    "Value": {
      "key1": "value1",
      "key2": "value2"
    }
  }
]

class

public class Item
{
    public string Name { get; set; }
    public Dictionary<string, string> Value { get; set; }
}

CodePudding user response:

One possible way would be to use a JsonConverter subclass.

This could e.g. contain a Dictionary<string, string> with the key-value pairs if a JTokenType.Object is found and return an empty dictionary in any other case. Can of course be customized as desired.

DictionaryValueConverter

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public class DictionaryValueConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(Dictionary<string, string>));
    }

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue,
        JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        return token.Type == JTokenType.Object 
            ? token.ToObject<Dictionary<string, string>>() 
            : new Dictionary<string, string>();
    }

    public override bool CanWrite => false;

    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Item

An Item of your array would then look like something like this:

using Newtonsoft.Json;

public class Item
{
    public string Name { get; set; }

    [JsonConverter(typeof(DictionaryValueConverter))]
    public Dictionary<string, string> Value { get; set; }
}

Test

You could then call this something like this:

using Newtonsoft.Json;

public static class Program
{
    static void Main()
    {
        string json = @"
        [
            {
                ""Name"": ""Name A"",
                ""Value"": ""Apples""
            },
            {
                ""Name"": ""Name B"",
                ""Value"": {
                    ""key1"": ""value1"",
                    ""key2"": ""value2""
                }
            }
            ]
        ";

        var list = JsonConvert.DeserializeObject<List<Item>>(json);

        if (list == null) return;
        foreach (var item in list)
        {
            Console.WriteLine($"name: {item.Name}");
            if (item.Value.Count == 0)
            {
                Console.WriteLine("no value");
            }
            foreach (var (key, value) in item.Value)
            {
                Console.WriteLine($" -> Key = {key}, Value = {value}");
            }
            Console.WriteLine();
        }
    }
}

The output of the above example code would be:

name: Name A
no value

name: Name B
 -> Key = key1, Value = value1
 -> Key = key2, Value = value2

As mentioned above, the approach can be customized as desired.

  • Related