将具有数组的json结构展平为多个没有数组的平面对象

时间:2018-08-28 12:13:05

标签: c# json recursion json.net

我不确定我是否正确地以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中的两行。

任何提示/技巧都非常感谢。谢谢。

3 个答案:

答案 0 :(得分:4)

您可以使用以下函数来做到这一点:

//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");

答案 1 :(得分:2)

使用外部库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

希望有帮助。

答案 2 :(得分:1)

如果您能够反序列化/序列化为强类型类,则可以使用自定义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可以根据Items(或您可能需要的任何其他属性)将其展平

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);
    }
}

要使用此Converter,请在序列化时提供它

        var invoice = JsonConvert.DeserializeObject<Invoice>(inputJson);
        var outputJson = JsonConvert.SerializeObject(invoice, new InvoiceFlattener());

您可能已经知道,在反序列化时此转换器不起作用,但是如果有此要求,则可以在ReadJson转换器方法中编写逻辑。 不利的一面是,如果Invoice类的结构发生变化,您将需要维护转换器。但这使我们处于一个强类型的世界