C#flattening json结构

时间:2011-09-12 21:59:17

标签: c# json data-structures recursion

我在C#中有一个json对象(表示为Newtonsoft.Json.Linq.JObject对象),我需要将其展平为字典。让我告诉你一个我的意思的例子:

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

这应该产生一个包含以下键值对的字典:

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

我该怎么做?

5 个答案:

答案 0 :(得分:23)

JObject jsonObject=JObject.Parse(theJsonString);
IEnumerable<JToken> jTokens = jsonObject.Descendants().Where(p => p.Count() == 0);
Dictionary<string, string> results = jTokens.Aggregate(new Dictionary<string, string>(), (properties, jToken) =>
                    {
                        properties.Add(jToken.Path, jToken.ToString());
                        return properties;
                    });

我有同样的要求将嵌套的json结构展平为字典对象。找到了解决方案here

答案 1 :(得分:4)

您可以使用https://github.com/jsonfx/jsonfx将json反序列化为动态对象。然后使用ExpandoObject获取所需内容。

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<string, object> dict = new Dictionary<string, object>();

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

        private void GenerateDictionary(System.Dynamic.ExpandoObject output, Dictionary<string, object> 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);
                    }
                }
            }
        }

答案 2 :(得分:1)

我实际上今天早些时候遇到同样的问题一开始就无法在SO上找到这个问题,最后编写了我自己的扩展方法来返回包含JSON blob的叶节点值的JValue对象。除了一些改进之外,它与接受的答案类似:

  1. 它处理您提供的任何JSON(数组,属性等),而不仅仅是JSON对象。
  2. 减少内存使用量
  3. 对最终不需要
  4. 的后代没有致电.Count()

    根据您的使用案例,这些可能相关或不相关,但它们适用于我的情况。我写过关于学习在my blog上展平JSON.NET对象的文章。这是我写的扩展方法:

    public static class JExtensions
    {
        public static IEnumerable<JValue> 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<JValue> GetLeafValuesFromJArray(JArray jArray)
        {
            for (var i = 0; i < jArray.Count; i++)
            {
                foreach (var result in GetLeafValues(jArray[i]))
                {
                    yield return result;
                }
            }
        }
    
        static IEnumerable<JValue> GetLeafValuesFromJProperty(JProperty jProperty)
        {
            foreach (var result in GetLeafValues(jProperty.Value))
            {
                yield return result;
            }
        }
    
        static IEnumerable<JValue> GetLeafValuesFromJObject(JObject jObject)
        {
            foreach (var jToken in jObject.Children())
            {
                foreach (var result in GetLeafValues(jToken))
                {
                    yield return result;
                }
            }
        }
    
        #endregion
    }
    

    然后在我的调用代码中,我只从提交的Path个对象中提取ValueJValue属性:

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

答案 3 :(得分:1)

从.NET Core 3.0开始,JsonDocument是一种方式(不需要Json.NET)。 我相信这会变得更容易。

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


public static Dictionary<string, JsonElement> 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 so that we can use the values outside of using
}

下面是更具表现力的版本。

测试

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

输出

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

更具表现力的版本

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

static Dictionary<string, JsonElement> 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 so that we can use the values outside of using
        }
    }


    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;
    }

答案 4 :(得分:0)

您可以使用JSONPath $..*获取JSON结构的所有成员,并过滤掉没有子元素的成员以跳过容器属性。

例如

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