【问题标题】:Object to separate KeyValue Pair collections对象来分离键值对集合
【发布时间】:2020-04-04 09:50:07
【问题描述】:

我有一个挑战,我需要获取任何对象并将其展平为键值对格式

这对于简单的类,甚至是我在其中包含其他类的类都非常有效

看一个例子,

public class Buyer
{
    [JsonProperty("name")]public string Name { get; set; }
    [JsonProperty("address")]public string Address { get; set; }        
    [JsonProperty("lastPurchase")]public Purchase LastPurchase { get; set; }

    public Buyer()
    {
        Name = "Joe Bloggs";
        Address = "An adddress somewhere";

        AllPurchases = new List<Purchase>()
        {
            new Purchase() {PurchaseAmount = 100, PurchaseDateTime = Convert.ToDateTime("2017-01-01")},
            new Purchase() {PurchaseAmount = 100, PurchaseDateTime = Convert.ToDateTime("2018-01-01")}
        };

        LastPurchase = new Purchase() {PurchaseAmount = 100, PurchaseDateTime = Convert.ToDateTime("2018-01-01")};
    }

    [JsonIgnore]
    public List<Purchase> AllPurchases { get; set; }

}

public class Purchase
{
    public DateTime PurchaseDateTime { get; set; }
    public double PurchaseAmount { get; set; }
}

下面的代码是我当前的实现

var buyer = new Buyer();
var json = JsonConvert.SerializeObject(buyer);
var obj = JObject.Parse(json);

var result = obj.Descendants()
            .OfType<JProperty>()
            .Where(s => s.Value.Type != JTokenType.Object)
            .Select(p => new KeyValuePair<string, string>(p.Path,
                p.Value.Type == JTokenType.Array || p.Value.Type == JTokenType.Object
                    ? null : p.Value.ToString()));


var serializerSettings = new JsonSerializerSettings
                         {
                             Formatting = Formatting.Indented,
                             ContractResolver = new CamelCasePropertyNamesContractResolver(),
                         };
var newJson = JsonConvert.SerializeObject(result, serializerSettings);
Console.WriteLine(newJson);

这会生成下面完美的Json

[
  {
    "key": "name",
    "value": "Joe Bloggs"
  },
  {
    "key": "address",
    "value": "An adddress somewhere"
  },
  {
    "key": "lastPurchase.PurchaseDateTime",
    "value": "01/01/2018 00:00:00"
  },
  {
    "key": "lastPurchase.PurchaseAmount",
    "value": "100"
  }
]

当我通过删除 JsonIgnore 来引入序列化列表时,事情变得很棘手

现在我明白了

[
  {
    "key": "name",
    "value": "Joe Bloggs"
  },
  {
    "key": "address",
    "value": "An adddress somewhere"
  },
  {
    "key": "lastPurchase.PurchaseDateTime",
    "value": "01/01/2018 00:00:00"
  },
  {
    "key": "lastPurchase.PurchaseAmount",
    "value": "100"
  },
  {
    "key": "allPurchases",
    "value": null
  },
  {
    "key": "allPurchases[0].PurchaseDateTime",
    "value": "01/01/2017 00:00:00"
  },
  {
    "key": "allPurchases[0].PurchaseAmount",
    "value": "100"
  },
  {
    "key": "allPurchases[1].PurchaseDateTime",
    "value": "01/01/2018 00:00:00"
  },
  {
    "key": "allPurchases[1].PurchaseAmount",
    "value": "100"
  }
]

这显然已经发生,因为我的逻辑中没有任何特定的处理列表

如何更改我的逻辑,使 AllPurchases 是一个键值对集合,键是 allPurchases[0]、allPurchases[1],值是单独的键值集合,这样可以避免像 allPurchases[0] 这样的键名.PurchaseAmount 等?

我需要保持解决方案的通用性,以便它将任何对象展平到这个结构中

保罗

【问题讨论】:

    标签: c# json dictionary key-value


    【解决方案1】:

    据我所知,您需要以下内容。 不在数组中的值和对象以 {key:"propertyPath", value:"valueTostring"} 的形式传输到 json 对象 子对象到键值对数组。 索引数组 {key:"property[index]", value:"valueTostringOrObjectKeyValueArray"} 以下

            var result = GetItmes(obj);
    
    
            IEnumerable<KeyValuePair<string,object>> GetItmes(in JToken token, string path = "")
            {
                return token switch
                {
                    JObject jObject => from prop in token.Children<JProperty>()
                                       from child in GetItmes(prop.Value, string.IsNullOrEmpty(path) ? prop.Name : $"{path}.{prop.Name}")
                                       select child,
                    JArray jArray => from item in jArray.Select((t, i) => (t, i))
                                     select new KeyValuePair<string, object>($"{path}[{item.i}]",GetItmes(item.t)),
                    JValue jValue => new[] { 
                        new KeyValuePair<string, object>(path, (object)jValue?.ToString()) 
                    },
                    _ => Enumerable.Empty<KeyValuePair<string, object>>(),
                };
            }
    

    将创建

        [
      {
        "key": "name",
        "value": "Joe Bloggs"
      },
      {
        "key": "address",
        "value": "An adddress somewhere"
      },
      {
        "key": "lastPurchase.PurchaseDateTime",
        "value": "1/1/2018 12:00:00 AM"
      },
      {
        "key": "lastPurchase.PurchaseAmount",
        "value": "100"
      },
      {
        "key": "AllPurchases[0]",
        "value": [
          {
            "key": "PurchaseDateTime",
            "value": "1/1/2017 12:00:00 AM"
          },
          {
            "key": "PurchaseAmount",
            "value": "100"
          }
        ]
      },
      {
        "key": "AllPurchases[1]",
        "value": [
          {
            "key": "PurchaseDateTime",
            "value": "1/1/2018 12:00:00 AM"
          },
          {
            "key": "PurchaseAmount",
            "value": "100"
          }
        ]
      }
    ]
    

    此代码是递归且未优化的,我相信有一种更有效的方法可以做到这一点。

    对于

    IEnumerable<KeyValuePair<string, object>> GetItmes(JToken token, string path = "")
            {
                switch (token)
                {
                    case JObject jObject:
                        return from prop in token.Children<JProperty>()
                               from child in GetItmes(prop.Value, string.IsNullOrEmpty(path) ? prop.Name : $"{path}.{prop.Name}")
                               select child;
                    case JArray jArray:
                        return from item in jArray.Select((t, i) => (t, i))
                               select new KeyValuePair<string, object>($"{path}[{item.i}]", GetItmes(item.t));
                    case JValue jValue:
                        return new[] {
                        new KeyValuePair<string, object>(path, (object)jValue?.ToString())
                    };
                    default: return Enumerable.Empty<KeyValuePair<string, object>>();
                };
            }
    

    【讨论】:

    • 谢谢,但我不认为我可以使用它,因为我得到一个 C# 版本错误,说递归模式在 C 7.3 中不可用,我认为我们的 piplines 不支持 c# 8
    • 你能告诉我版本吗?这个查询可以很容易地用任何 c# 版本编写。这样会更短。
    • 我使用 7.3 和 .net core 2.2 和完整的 4.7.2
    • 新增7.3版本只是换了个开关。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-05
    • 1970-01-01
    • 1970-01-01
    • 2018-03-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多