Search code examples
c#jsonvariablesdeserialization

How to deserialize a JSON with variable propertynames


This is the weirdest json I have ever had to deserialize. It has variable propertynames in each item of an array:

{
    "id": 1000,
    "rows": [
        {
            "costPriceIncludingVat": 1000,
            "productInfo": {
                "depth": 600,
                "width": 2200,
                "parts": {
                    "876-08533": {
                        "depth": 0,
                        "width": 0,
                        "description": "Thing 3",
                        "properties": {
                            "measure": "0"
                        }
                    },
                    "cabinet-1": {
                        "depth": 600,
                        "width": 746,
                        "parts": {
                            "JW_876-76117_P": {
                                "depth": 0,
                                "width": 0,
                                "description": "Thing 1",
                                "properties": {
                                    "measure": "0"
                                }
                            }
                        },
                        "description": "Closet"
                    }
                },
                "description": "My description"
            }
        }
    ],
    "quotation": "ABCDEF"
}

I have the 'productInfo'-node as this C# class:

    public class ProductInfo
    {
        public int Depth;
        public int Width;
        public List<ProductInfo> Parts;
        public string Description;
        public Properties Properties;
    }

Never mind the discrepencies in propertynames (casing).

Each node in 'parts' is a ProductInfo, but the names of the nodes are not named 'productInfo' so the deserializer does not know how to deserialize it as.

I've used System.Text.Json but it does not deserialize at all. I just get a new instance of the root class. Newtonsoft.Json does it better, but raises an exception:

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[ConsoleTestApp.IvenzaTests.Models.ProductInfo]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.

How do I make the deserializer recognize each item in the parts-array as a ProductInfo? Preferrably using System.Text.Json.


Solution

  • according to your json, your ProductInfo is a nested structure, so you should also expose some level above

    public class Root
    {
        public int Id { get; set; }
        public List<Row> Rows { get; set; }
        public string Quotation { get; set; }
    }
    
    public class Row
    {
        public decimal CostPriceIncludingVat { get; set; }
        public ProductInfo ProductInfo { get; set; }
    }
    

    And in ProductInfo it looks like Parts have to be a Dictionary not a list, as you have key-value presented in your json

    public class ProductInfo
    {
        public int Depth { get; set; }
        public int Width { get; set; }
        public Dictionary<string, ProductInfo> Parts { get; set; } // Handle dictionary of parts
        public string Description { get; set; }
        public Properties Properties { get; set; }
    }
    

    if you re-arrange data structures, it should be quite straight forward to read the data

    var root = JsonConvert.DeserializeObject<Root>(json);
    
    // Accessing the data
    foreach (var row in root.Rows)
    {
        Console.WriteLine($"Product Depth: {row.ProductInfo.Depth}, Description: {row.ProductInfo.Description}");
        foreach (var partKey in row.ProductInfo.Parts.Keys)
        {
            var part = row.ProductInfo.Parts[partKey];
            Console.WriteLine($"  Part {partKey} - Description: {part.Description}");
        }
    }