How to cleanly deserialize JSON where string values are wrapped in objects of the same name
Asked Answered
S

2

7

I want to deserialize some strange JSON to C# classes:

{
    "Result": {
        "Client": {
            "ProductList": {
                "Product": [
                    {
                        "Name": {
                            "Name": "Car polish"
                        }
                    }
                ]
            },
            "Name": {
                "Name": "Mr. Clouseau"
            },
            "AddressLine1": {
                "AddressLine1": "Hightstreet 13"
            }
        }
    }
}

json2csharp generates the following classes for the JSON:

public class Name
{
    public string Name { get; set; }
}

public class Product
{
    public Name Name { get; set; }
}

public class ProductList
{
    public List<Product> Product { get; set; }
}

public class Name2
{
    public string Name { get; set; }
}

public class AddressLine1
{
    public string AddressLine1 { get; set; }
}

public class Client
{
    public ProductList ProductList { get; set; }
    public Name2 Name { get; set; }
    public AddressLine1 AddressLine1 { get; set; }
}

public class Result
{
    public Client Client { get; set; }
}

public class RootObject
{
    public Result Result { get; set; }
}

The problem is that the duplicated property names in the objects (Name in Product and Client, AddressLine1 in Client) forces me to create an extra class with only one string property (Name, AddressLine1) to be able to deserialize the JSON.

The generated code is also invalid, because member names cannot be the same as their enclosing type (but I know that can be solved using the [JsonProperty(PropertyName = "Name")] attribute).

What's the best way to avoid that unnecessary level in the class hierarchy and have a clean class structure to be able to deserialize this JSON using JSON.NET? Note this is a third-party API, so I can't just change the JSON.

Sycee answered 13/2, 2014 at 8:19 Comment(3)
Remove one of the Name or Name2.Bibbye
Can't you change the json generation process? This may be a better solution if the output is cleaner!Archipenko
No, I cannot change the JSON. It is received from a public REST service.Sycee
C
3

Indeed, this is a strange format for an API result, making it more difficult to consume. One idea to solve the problem is to create a custom JsonConverter that can take a wrapped value and return the inner value as if the wrapper were not there. This would allow you to deserialize the clunky JSON into a more sensible class hierarchy.

Here is a converter that should work:

class WrappedObjectConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        // Get the value of the first property of the inner object
        // and deserialize it to the requisite object type
        return token.Children<JProperty>().First().Value.ToObject(objectType);
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Armed with this converter, you can create a class hierarchy that eliminates the extra levels of nesting. You must mark the properties that need to be "unwrapped" with a [JsonConverter] attribute so that Json.Net knows when to apply the custom converter. Here is the improved class structure:

public class RootObject
{
    public Result Result { get; set; }
}

public class Result
{
    public Client Client { get; set; }
}

public class Client
{
    [JsonConverter(typeof(WrappedObjectConverter))]
    public List<Product> ProductList { get; set; }

    [JsonConverter(typeof(WrappedObjectConverter))]
    public string Name { get; set; }

    [JsonConverter(typeof(WrappedObjectConverter))]
    public string AddressLine1 { get; set; }
}

public class Product
{
    [JsonConverter(typeof(WrappedObjectConverter))]
    public string Name { get; set; }
}

(Note that if the Result object will not contain any other properties besides Client, you can apply the WrappedObjectConverter there as well to move the Client up to the RootObject and eliminate the Result class.)

Here is a demo showing the converter in action:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        {
            ""Result"": {
                ""Client"": {
                    ""ProductList"": {
                        ""Product"": [
                            {
                                ""Name"": {
                                    ""Name"": ""Car polish""
                                }
                            }
                        ]
                    },
                    ""Name"": {
                        ""Name"": ""Mr. Clouseau""
                    },
                    ""AddressLine1"": {
                        ""AddressLine1"": ""Hightstreet 13""
                    }
                }
            }
        }";

        RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);

        Client client = obj.Result.Client;
        foreach (Product product in client.ProductList)
        {
            Console.WriteLine(product.Name);
        }
        Console.WriteLine(client.Name);
        Console.WriteLine(client.AddressLine1);
    }
}

Output:

Car polish
Mr. Clouseau
Hightstreet 13
Cloudscape answered 13/2, 2014 at 16:41 Comment(4)
This is a great start for me, thanks! I have the added complication that my nested property is not alone. So I have the parse all of it but treat that one field special. Here's an example: "Address": { "@attributes": { "AddressType": "prop" }, "Address": "12 st", "City": "B", "State": "MD", "PostalCode": "12", "Country": "US", "Email": "[email protected]" }Trula
Why do we need to repeat "Name" and "AddressLine1". Can we not make the deserialiser work with a simpler string such as: { "Result": { "Client": { "ProductList": [ { "Name": "Car polish" } ], "Name": "Mr. Clouseau", "AddressLine1": "Hightstreet 13" } } }Tissue
@Tissue Of course; the ideal solution is to make the JSON simpler and easier to consume. The problem is that the JSON is often controlled by a third-party API, so you as a consumer have to deal with it the way it is, unless you can convince the API provider to change it. The point of this answer is to show how to deal with the flawed JSON if you can't change it.Cloudscape
Thanks a lot, I created the following post: #67300296Tissue
F
2

It sounds like you may be interesting in implementing a custom JsonConverter. Here's a site that has some samples of how you could do this. It's a fairly simple process and would allow you to keep the JSON you're stuck with while having whatever class structure you're most comfortable with.

Francinefrancis answered 13/2, 2014 at 8:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.