将带有数组的 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 结构展平为多个没有数组的平面对象的主要内容,如果未能解决你的问题,请参考以下文章

火花红移。将数组展平为字符串

具有数组和字典混合的横向展平雪管数据

使用 jq 为 JSON 对象的嵌套数组中的属性展平数组

雪花中具有多个 JSON 对象的横向展平数组

r JSON到CSV转换器。使用`jsonlite` R包,展平所有层次结构并将所有剩余的列表/数组转换为strin

在 PHP 中展平 JSON 多级数组