【问题标题】:flattening json structure with arrays to multiple flat objects without arrays将带有数组的 json 结构展平为多个没有数组的平面对象
【发布时间】: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"
}]

因此,“发票”中的每个“订单项”都应该“导致”新发票中包含重复的其他元素。

可以接受围绕结果数据结构的小变化,我可以围绕它调整我的代码。 我一直在使用几个类似的问题,例如:

有关更多背景信息,我需要将其用于 CSV 导出。所以结果集应该是生成的 CSV 中的两行。

非常感谢任何提示/提示。谢谢。

【问题讨论】:

  • 只是为了澄清您是否正在使用第一个 JSON 结构并希望将其重组为第二个结构?还是您的 C# 类生成第一个 JSON 结构,而您希望它生成第二个 JSON 结构(实际上第一个 JSON 结构不应该存在)
  • @Skintkingle First 是正确的 - 我正在使用第一个 JSON 结构,我需要将其重组为第二个结构。
  • 你现在有一个可以很好地反序列化第一个示例的 C# 类吗?如果是这样,您能否在问题中提供该类。 :)
  • @Skintkingle 由于动态数据结构,我正在使用 object/JObject/JToken 等,所以(遗憾的是)我没有固定的 c# 类
  • 那么您正在读取的 JSON 具有未知的数据结构?或者它只是有条件地有时不在这里或那里提供参数?

标签: c# json recursion json.net


【解决方案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”。无论如何,我已经接受了你的回答,因为它让我知道如何进行。谢谢
猜你喜欢
  • 2013-03-27
  • 2019-01-17
  • 2021-01-14
  • 1970-01-01
  • 2018-12-01
  • 2019-08-10
  • 1970-01-01
  • 2022-12-06
  • 1970-01-01
相关资源
最近更新 更多