C#扁平化json结构

Posted

技术标签:

【中文标题】C#扁平化json结构【英文标题】:C# flattening json structure 【发布时间】:2011-11-15 17:03:38 【问题描述】:

我在 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"

我该怎么做?

【问题讨论】:

您可以查看输入的 json 图形,然后对所述图形执行详尽的遍历,在每个节点访问时添加到结果集 我能得到一些代码或链接来彻底遍历所述图吗? en.wikipedia.org/wiki/Tree_traversal fyi - 我认为你实际上想要添加到叶节点上的结果集并附加到非叶节点上的结果键...... 【参考方案1】:
JObject jsonObject=JObject.Parse(theJsonString);
IEnumerable<JToken> jTokens = jsonObject.Descendants().Where(p => !p.HasValues);
Dictionary<string, string> results = jTokens.Aggregate(new Dictionary<string, string>(), (properties, jToken) =>
                    
                        properties.Add(jToken.Path, jToken.ToString());
                        return properties;
                    );

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

【讨论】:

我爱你。你救了我的命 如果不使用Count(),性能可能会有所提高:即...Where(p =&gt; !p.HasValues) @Gebb 是的,我会使用 .Where(p =&gt; !p.Any())【参考方案2】:

基于 tymtam 提供的代码,但也支持数组:

    public static IEnumerable<KeyValuePair<string, string>> Flatten<T>(this T data, string seperator = ":") where T : class
    
        var json = JsonSerializer.Serialize(data);

        string GetArrayPath(string path, string name, int idx) =>
            path == null ? $"nameseperatoridx" : $"pathseperatornameseperatoridx";
        IEnumerable<(string Path, JsonElement Element)> GetLeaves(string path, string name, JsonElement element) => element.ValueKind switch
        
            JsonValueKind.Object => element.EnumerateObject()
                .SelectMany(child => GetLeaves(path == null ? name : $"pathseperatorname", child.Name, child.Value)),
            JsonValueKind.Array => element.EnumerateArray()
                .SelectMany((child, idx) => child.ValueKind == JsonValueKind.Object
                        ? child.EnumerateObject().SelectMany(child => GetLeaves(GetArrayPath(path, name, idx), child.Name, child.Value))
                        : new[]  (Path: GetArrayPath(path, name, idx), child) 
                    ),
            _ => new[]  (Path: path == null ? name : $"pathseperatorname", element) ,
        ;

        using JsonDocument document = JsonDocument.Parse(json); // Optional JsonDocumentOptions options
        return document.RootElement.EnumerateObject()
            .SelectMany(p => GetLeaves(null, p.Name, p.Value))
            .ToDictionary(k => k.Path, v => v.Element.Clone().ToString()); //Clone so that we can use the values outside of using
    

【讨论】:

【参考方案3】:

从 .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;
    

【讨论】:

用于将 json appSettings 转换为 AppService 配置(使用双下划线而不是点)【参考方案4】:

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

例如

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

【讨论】:

【参考方案5】:

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

    它处理您提供给它的任何 JSON(数组、属性等),而不仅仅是 JSON 对象。 内存使用更少 不要对您最终不需要的后代调用.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

然后在我的调用代码中,我只是从返回的JValue 对象中提取PathValue 属性:

var jToken = JToken.Parse("blah blah json here"); 
foreach (var jValue in jToken.GetLeafValues()) 

    Console.WriteLine("0 = 1", jValue.Path, jValue.Value);

【讨论】:

【参考方案6】:

您可以使用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);
                    
                
            
        

【讨论】:

他不想用 Newtonsoft 的 JSON 序列化器来做这个吗? 是的,他可以在 newtonsoft 对象上使用反射,并遍历那里的属性,但是,他可以轻松获取 JSON 字符串,并将其插入其中并获得他需要的结果。跨度> 其实,你们都说对了一部分。我正在使用 Newtonsoft 的 JSON 序列化器,但我不需要使用反射。不过,我很喜欢你的 GenerateDictionary 方法,我只会重写我的对象。

以上是关于C#扁平化json结构的主要内容,如果未能解决你的问题,请参考以下文章

Javascript中扁平化数据结构与JSON树形结构转换详解

RestKit 0.20:在 JSON 中使用动态扁平化层次结构

RestKit 0.2:扁平化 JSON 中的层次结构

JSON多层嵌套复杂结构数据扁平化处理转为行列数据

JavaScript实现扁平数组结构与JSON树形结构相互转换递归reducecontinuepushconcatfor of

带有 ORDER BY 的雪花 JSON 扁平化