Is there a more elegant way to get specific value from JSON object using system.text.json

Viewed 1309

Consider this Json object:

{
    "main": {
        "2058": {
            "id": "2058",
            "name": "foo",
            "attrib1": "17",
            "domain": "somewhere.net",
            "enabled": "1",
            "permissions": [
                "admin",
                "user",
                "guest",
                "vpn",
                "power"
            ]
        }
    },
    "validate": {
        "2058": {
            "id": "2058",
            "name": "foo",
            "attrib1": "17",
            "domain": "somewhere.net",
            "enabled": "1",
            "permissions": [
                "admin",
                "user",
                "guest",
                "vpn",
                "power"
            ]
        }
    },
    "result": "ok"
}

Between "enabled" and "permissions", there are quite a few hundred more pairs witch i left out. The "validate" part is an exact copy of "main" and the "result" is, well ya, the result.

I want to get 2 specific values from the main section. "id" and "domain". I use the following code to do so.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;

namespace JsonParse
{
    class Program
    {
        static void Main()
        {
            string json = "{" +
                "\"main\":{" +
                    "\"2058\":{" +
                        "\"id\":\"2058\"," +
                        "\"name\":\"foo\"," +
                        "\"attrib1\":\"17\"," +
                        "\"domain\":\"somewhere.net\"," +
                        "\"enabled\":\"1\"," +
                        "\"permissions\":[\"admin\",\"user\",\"guest\",\"vpn\",\"power\"]" +
                    "}" +
                "}," +
                "\"validate\":{" +
                    "\"2058\":{" +
                        "\"id\":\"2058\"," +
                        "\"name\":\"foo\"," +
                        "\"attrib1\":\"17\"," +
                        "\"domain\":\"somewhere.net\"," +
                        "\"enabled\":\"1\"," +
                        "\"permissions\":[\"admin\",\"user\",\"guest\",\"vpn\",\"power\"]" +
                    "}" +
                "}," +
                "\"result\":\"ok\"" +
            "}";

            foreach (KeyValuePair<string, object> item in JsonSerializer.Deserialize<Dictionary<string, object>>(json))
            {
                if (item.Key == "main")
                {
                    Dictionary<string, object> pairs = JsonSerializer.Deserialize<Dictionary<string, object>>(item.Value.ToString());
                    Dictionary<string, object> pairs1 = JsonSerializer.Deserialize<Dictionary<string, object>>(pairs.First().Value.ToString());
                    foreach (KeyValuePair<string, object> valuePair in pairs1)
                    {
                        if (valuePair.Key == "id" || valuePair.Key == "domain")
                        {
                            Console.WriteLine("Key: " + valuePair.Key + "\nValue: " + valuePair.Value);
                        }
                    }
                }
            }
        }
    }
}

From this, i get "2058" and "somewhere.net" but is there are more elegant way to get the values instead of deserialize 3 times?

2 Answers

If you are using System.Text.Json API, you can parse your JSON to JsonDocument first, then enumerate its properties. Get the main object first, then flatten its child properties and find required values by id and domain names

using var document = JsonDocument.Parse(jsonString);

var main = document.RootElement.EnumerateObject().FirstOrDefault(p => p.Name == "main");
var properties = main.Value.EnumerateObject().SelectMany(p => p.Value.EnumerateObject()).ToList();
var id = properties.FirstOrDefault(p => p.Name == "id").Value.GetString();
var domain = properties.FirstOrDefault(p => p.Name == "domain").Value.GetString();

If you only need the main property you can use json path to select all "mains" via Newtonsoft Json.NET:

var pairs = JObject.Parse(js).SelectTokens("$.main.*")
    .Select(j => (id:j["id"],domain: j["domain"]))
    .ToList();

or just deserialize to corresponding structure (either with System.Text.Json or Newtonsoft):

class MyClass
{
    public Dictionary<string, InnerClass> main { get; set; }
}

class InnerClass
{
    public string id { get; set; }
    public string domain { get; set; }
}

ICollection<InnerClass> mains= JsonSerializer.Deserialize<MyClass>(js).main.Values; // System.Text.Json
Related