33 Stimmen

C# JSON-Struktur flachhalten

Ich habe ein JSON-Objekt in C# (dargestellt als ein Newtonsoft.Json.Linq.JObject-Objekt) und muss es in ein Dictionary umwandeln. Lass mich dir anhand eines Beispiels zeigen, was ich meine:

{
    "name": "test",
    "father": {
         "name": "test2"
         "age": 13,
         "dog": {
             "color": "brown"
         }
    }
}

Dies sollte ein Dictionary mit den folgenden Schlüssel-Wert-Paaren ergeben:

["name"] == "test",
["father.name"] == "test2",
["father.age"] == 13,
["father.dog.color"] == "brown"

Wie kann ich das machen?

41voto

Sarath Rachuri Punkte 1956
JObject jsonObject=JObject.Parse(theJsonString);
IEnumerable jTokens = jsonObject.Descendants().Where(p => !p.HasValues);
Dictionary results = jTokens.Aggregate(new Dictionary(), (properties, jToken) =>
                    {
                        properties.Add(jToken.Path, jToken.ToString());
                        return properties;
                    });

Ich hatte dieselbe Anforderung, eine verschachtelte JSON-Struktur in ein Wörterbuchobjekt umzuwandeln. Die Lösung habe ich hier gefunden.

7voto

tymtam Punkte 23765

Ab .NET Core 3.0 JsonDocument ist ein Weg (Json.NET ist nicht erforderlich). Ich bin sicher, dass es einfacher wird.

using System.Linq;
using System.Text.Json;
(...)

public static Dictionary GetFlat(string json)
{
    IEnumerable<(string Path, JsonProperty P)> GetLeaves(string path, JsonProperty p)
        => p.Value.ValueKind != JsonValueKind.Object
            ? new[] { (Path: path == null ? p.Name : path + "." + p.Name, p) }
            : p.Value.EnumerateObject() .SelectMany(child => GetLeaves(path == null ? p.Name : path + "." + p.Name, child));

    using (JsonDocument document = JsonDocument.Parse(json)) // Optional JsonDocumentOptions options
        return document.RootElement.EnumerateObject()
            .SelectMany(p => GetLeaves(null, p))
            .ToDictionary(k => k.Path, v => v.P.Value.Clone()); //Clone damit wir die Werte außerhalb von using verwenden können
}

Eine ausdrucksstärkere Version wird unten gezeigt.

Test

using System.Linq;
using System.Text.Json;
(...)

var json = @"{
    ""name"": ""test"",
    ""father"": {
            ""name"": ""test2"", 
         ""age"": 13,
         ""dog"": {
                ""color"": ""brown""
         }
        }
    }";

var d = GetFlat(json);
var options2 = new JsonSerializerOptions { WriteIndented = true };
Console.WriteLine(JsonSerializer.Serialize(d, options2));

Ausgabe

{
  "name": "test",
  "father.name": "test2",
  "father.age": 13,
  "father.dog.color": "brown"
}

Ausdrucksstärkere Version

using System.Linq;
using System.Text.Json;
(...)

static Dictionary GetFlat(string json)
    {
        using (JsonDocument document = JsonDocument.Parse(json))
        {
            return document.RootElement.EnumerateObject()
                .SelectMany(p => GetLeaves(null, p))
                .ToDictionary(k => k.Path, v => v.P.Value.Clone()); //Clone damit wir die Werte außerhalb von using verwenden können
        }
    }

    static IEnumerable<(string Path, JsonProperty P)> GetLeaves(string path, JsonProperty p)
    {
        path = (path == null) ? p.Name : path + "." + p.Name;
        if (p.Value.ValueKind != JsonValueKind.Object)
            yield return (Path: path, P: p);
        else
            foreach (JsonProperty child in p.Value.EnumerateObject())
                foreach (var leaf in GetLeaves(path, child))
                    yield return leaf;
    }

5voto

bencobb Punkte 618

Sie können https://github.com/jsonfx/jsonfx verwenden, um JSON in ein dynamisches Objekt zu deserialisieren. Verwenden Sie dann das ExpandoObject, um das zu erhalten, was Sie wollen.

public Class1()
        {
            string json = @"{
                                ""name"": ""test"",
                                ""father"": {
                                     ""name"": ""test2"",
                                     ""age"": 13,
                                     ""dog"": {
                                         ""color"": ""brown""
                                     }
                                }
                            }";

            var reader = new JsonFx.Json.JsonReader();
            dynamic output = reader.Read(json);
            Dictionary dict = new Dictionary();

            GenerateDictionary((System.Dynamic.ExpandoObject) output, dict, "");
        }

        private void GenerateDictionary(System.Dynamic.ExpandoObject output, Dictionary dict, string parent)
        {
            foreach (var v in output)
            {
                string key = parent + v.Key;
                object o = v.Value;

                if (o.GetType() == typeof(System.Dynamic.ExpandoObject))
                {
                    GenerateDictionary((System.Dynamic.ExpandoObject)o, dict, key + ".");
                }
                else
                {
                    if (!dict.ContainsKey(key))
                    {
                        dict.Add(key, o);
                    }
                }
            }
        }

5voto

Ich hatte tatsächlich das gleiche Problem heute früher und konnte diese Frage nicht sofort auf SO finden, also schrieb ich meine eigene Erweiterungsmethode, um die JValue-Objekte zurückzugeben, die die Blattknotenwerte des JSON-Blobs enthalten. Es ähnelt der akzeptierten Antwort, aber mit einigen Verbesserungen:

  1. Es verarbeitet jedes JSON, das Sie ihm geben (Arrays, Eigenschaften usw.) anstelle eines einfachen JSON-Objekts.
  2. Weniger Speicherplatzverbrauch
  3. Keine Aufrufe von .Count() bei Nachfolgern, die Sie letztendlich nicht benötigen

Je nach Anwendungsfall können diese relevant sein oder auch nicht, aber in meinem Fall sind sie es. Ich habe auf meinem Blog darüber geschrieben, wie man die JSON.NET-Objekte abflacht: my blog. Hier ist die von mir geschriebene Erweiterungsmethode:

public static class JExtensions
{
    public static IEnumerable GetLeafValues(this JToken jToken)
    {
        if (jToken is JValue jValue)
        {
            yield return jValue;
        }
        else if (jToken is JArray jArray)
        {
            foreach (var result in GetLeafValuesFromJArray(jArray))
            {
                yield return result;
            }
        }
        else if (jToken is JProperty jProperty)
        {
            foreach (var result in GetLeafValuesFromJProperty(jProperty))
            {
                yield return result;
            }
        }
        else if (jToken is JObject jObject)
        {
            foreach (var result in GetLeafValuesFromJObject(jObject))
            {
                yield return result;
            }
        }
    }

    #region Private helpers

    static IEnumerable GetLeafValuesFromJArray(JArray jArray)
    {
        for (var i = 0; i < jArray.Count; i++)
        {
            foreach (var result in GetLeafValues(jArray[i]))
            {
                yield return result;
            }
        }
    }

    static IEnumerable GetLeafValuesFromJProperty(JProperty jProperty)
    {
        foreach (var result in GetLeafValues(jProperty.Value))
        {
            yield return result;
        }
    }

    static IEnumerable GetLeafValuesFromJObject(JObject jObject)
    {
        foreach (var jToken in jObject.Children())
        {
            foreach (var result in GetLeafValues(jToken))
            {
                yield return result;
            }
        }
    }

    #endregion
}

Dann in meinem Aufrufcode extrahiere ich einfach die Path- und Value-Eigenschaften aus den zurückgegebenen JValue-Objekten:

var jToken = JToken.Parse("blah blah json here"); 
foreach (var jValue in jToken.GetLeafValues()) 
{
    Console.WriteLine("{0} = {1}", jValue.Path, jValue.Value);
}

2voto

H77 Punkte 5667

Sie könnten den JSONPath $..* verwenden, um alle Elemente der JSON-Struktur zu erhalten und diejenigen ohne Kinder auszufiltern, um die Container-Eigenschaften zu überspringen.

zum Beispiel

var schemaObject = JObject.Parse(schema);
var values = schemaObject
    .SelectTokens("$..*")
    .Where(t => !t.HasValues)
    .ToDictionary(t => t.Path, t => t.ToString());

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X