Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 357
domenu Avatar asked Feb 13 '14 08:02

domenu


People also ask

How do I deserialize JSON to an object?

NET objects (deserialize) A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.

What is serialized and deserialized in JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).

Is polymorphic deserialization possible in system text JSON?

Text. Json doesn't support the serialization of polymorphic type hierarchies. For example, if a property's type is an interface or an abstract class, only the properties defined on the interface or abstract class are serialized, even if the runtime type has additional properties.

What is Jsonconvert?

Provides methods for converting between . NET types and JSON types.


2 Answers

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
like image 125
Brian Rogers Avatar answered Nov 09 '22 19:11

Brian Rogers


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.

like image 2
5 revs, 3 users 96% Avatar answered Nov 09 '22 20:11

5 revs, 3 users 96%