将带有数组的 json 结构展平为多个没有数组的平面对象
Posted
技术标签:
【中文标题】将带有数组的 json 结构展平为多个没有数组的平面对象【英文标题】:flattening json structure with arrays to multiple flat objects without arrays 【发布时间】:2019-02-03 01:43:39 【问题描述】:我不确定我是否 100% 正确地描述了主题中的问题,但我相信这些示例可以解决问题。
我有如下 JSON 结构(注意:这可能会改变,所以我需要倾向于通用解决方案)
一张包含多个行项目的发票:
"contactName": "Company",
"lineItems": [
"quantity": 7.0,
"description": "Beer No* 45.5 DIN KEG"
,
"quantity": 2.0,
"description": "Beer Old 49.5 DIN KEG"
],
"invoiceNumber": "C6188372"
这是想要的结果数据结构(具有重复数据和不同行项目信息的多张发票):
[
"contactName": "Company",
"quantity": 7.0,
"description": "Beer No* 45.5 DIN KEG"
"invoiceNumber": "C6188372"
,
"contactName": "Company",
"quantity": 2.0,
"description": "Beer Old 49.5 DIN KEG"
"invoiceNumber": "C6188372"
]
因此,“发票”中的每个“订单项”都应该“导致”新发票中包含重复的其他元素。
可以接受围绕结果数据结构的小变化,我可以围绕它调整我的代码。 我一直在使用几个类似的问题,例如:
C# flattening json structure Flatten an array of objects that may contain arrays How can I flatten a collection of objects (which in turn contain collections)? Generically Flatten Json using c#(等)。我相信这是最接近的解决方案?但不确定是否有更好的方法有关更多背景信息,我需要将其用于 CSV 导出。所以结果集应该是生成的 CSV 中的两行。
非常感谢任何提示/提示。谢谢。
【问题讨论】:
只是为了澄清您是否正在使用第一个 JSON 结构并希望将其重组为第二个结构?还是您的 C# 类生成第一个 JSON 结构,而您希望它生成第二个 JSON 结构(实际上第一个 JSON 结构不应该存在) @Skintkingle First 是正确的 - 我正在使用第一个 JSON 结构,我需要将其重组为第二个结构。 你现在有一个可以很好地反序列化第一个示例的 C# 类吗?如果是这样,您能否在问题中提供该类。 :) @Skintkingle 由于动态数据结构,我正在使用 object/JObject/JToken 等,所以(遗憾的是)我没有固定的 c# 类 那么您正在读取的 JSON 具有未知的数据结构?或者它只是有条件地有时不在这里或那里提供参数? 【参考方案1】:使用外部库 Cinchoo ETL - 一个开源库,您可以通过几行代码将您的 JSON 转换为预期的 CSV 格式
string json = @"
""contactName"": ""Company"",
""lineItems"": [
""quantity"": 7.0,
""description"": ""Beer No* 45.5 DIN KEG""
,
""quantity"": 2.0,
""description"": ""Beer Old 49.5 DIN KEG""
],
""invoiceNumber"": ""C6188372""
";
StringBuilder sb = new StringBuilder();
using (var p = ChoJSONReader.LoadText(json))
using (var w = new ChoCSVWriter(sb)
.WithFirstLineHeader()
)
w.Write(p
.SelectMany(r1 => ((dynamic[])r1.lineItems).Select(r2 => new
r1.contactName,
r2.quantity,
r2.description,
r1.invoiceNumber
)));
Console.WriteLine(sb.ToString());
输出 CSV:
contactName,quantity,description,invoiceNumber
Company,7,Beer No* 45.5 DIN KEG,C6188372
Company,2,Beer Old 49.5 DIN KEG,C6188372
希望对你有帮助。
【讨论】:
与此同时,我已经使用 DavidGs 回答完成了我需要的工作,但是感谢您提供信息!我不知道这个图书馆。我去看看。【参考方案2】:如果您能够反序列化/序列化为强类型类,则可以使用自定义 JsonConverter
。
我想发票信息应该在一些半结构化的对象中,所以这应该是可行的:
public class Invoice
public string ContactName get; set;
public List<Item> LineItems get; set; = new List<Item>();
public string InvoiceNumber get; set;
public class Item
public double Quantity get; set;
public string Description get; set;
然后使用 JsonConverter,您可以根据项目(或您可能想要的任何其他属性/属性)将其展平
public class InvoiceFlattener : JsonConverter
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var obj = value as Invoice;
if (obj == null)
return;
writer.WriteStartArray();
foreach (var item in obj.LineItems)
writer.WriteStartObject();
writer.WritePropertyName(nameof(obj.ContactName));
writer.WriteValue(obj.ContactName);
writer.WritePropertyName(nameof(item.Quantity));
writer.WriteValue(item.Quantity);
writer.WritePropertyName(nameof(item.Description));
writer.WriteValue(item.Description);
writer.WritePropertyName(nameof(obj.InvoiceNumber));
writer.WriteValue(obj.InvoiceNumber);
writer.WriteEndObject();
writer.WriteEndArray();
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
throw new NotImplementedException();
public override bool CanConvert(Type objectType)
return objectType == typeof(Invoice);
为了使用这个转换器,你在序列化时提供它
var invoice = JsonConvert.DeserializeObject<Invoice>(inputJson);
var outputJson = JsonConvert.SerializeObject(invoice, new InvoiceFlattener());
你可能已经知道这个转换器在反序列化时不起作用,但如果这是一个要求,你可以在 ReadJson 转换器方法中编写逻辑。
这样做的缺点是,如果 Invoice
类的结构发生变化,您将需要维护转换器。但它让我们处于一个强类型的世界中
【讨论】:
不幸的是,在这种情况下,我并不生活在强类型世界中 - 输入数据结构由外部 Json 模式“指定”,可以在任何给定时间修改,我需要适应它: ( 在这种情况下,我希望包含您的 lineItems 列表的属性不会改变! DavidGs 的答案可能就是你想要的。 我刚刚在他的答案下面写了一条评论:我将把他的答案扩展为更通用一点(检测数组),这样我就不需要在代码中硬编码“lineItems”字符串。不过感谢您的帮助。【参考方案3】:你可以用这样的函数来做到这一点:
//Pass in the name of the array property you want to flatten
public string FlattenJson(string input, string arrayProperty)
//Convert it to a JObject
var unflattened = JsonConvert.DeserializeObject<JObject>(input);
//Return a new array of items made up of the inner properties
//of the array and the outer properties
var flattened = ((JArray)unflattened[arrayProperty])
.Select(item => new JObject(
unflattened.Properties().Where(p => p.Name != arrayProperty),
((JObject)item).Properties()));
//Convert it back to Json
return JsonConvert.SerializeObject(flattened);
然后这样称呼它:
var flattenedJson = FlattenJson(inputJson, "lineItems");
【讨论】:
我已经尝试过了,我认为这可以解决问题。我需要将其扩展为递归并检测哪些属性是数组,这样我就不需要在代码中显式编写“lineItems”。无论如何,我已经接受了你的回答,因为它让我知道如何进行。谢谢以上是关于将带有数组的 json 结构展平为多个没有数组的平面对象的主要内容,如果未能解决你的问题,请参考以下文章