首页 > 解决方案 > 用于分隔 KeyValue Pair 集合的对象

问题描述

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

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

看一个例子,

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#jsondictionarykey-value

解决方案


据我所知,您想要以下内容。不在数组中的值和对象以 {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>>();
            };
        }

推荐阅读