【问题标题】:C# flattening json structureC#扁平化json结构
【发布时间】: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 图形,然后对所述图形执行详尽的遍历,在每个节点访问时添加到结果集
  • 我能得到一些代码或链接来彻底遍历所述图吗?
  • fyi - 我认为你实际上想要添加到叶节点上的结果集并附加到非叶节点上的结果键......

标签: c# json data-structures recursion


【解决方案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 ? $"{name}{seperator}{idx}" : $"{path}{seperator}{name}{seperator}{idx}";
        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 : $"{path}{seperator}{name}", 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 : $"{path}{seperator}{name}", 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 对象。它类似于接受的答案,除了一些改进:

      1. 它处理您提供给它的任何 JSON(数组、属性等),而不仅仅是 JSON 对象。
      2. 内存使用更少
      3. 不要对您最终不需要的后代调用.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 方法,我只会重写我的对象。
        猜你喜欢
        • 2021-11-19
        • 1970-01-01
        • 1970-01-01
        • 2017-04-17
        • 2017-04-22
        • 1970-01-01
        • 1970-01-01
        • 2022-09-26
        • 1970-01-01
        相关资源
        最近更新 更多