user4807273
user4807273

Reputation: 1

How to deserialize JSON with cross reference?

I have such JSON:

{
    "data": {
        "name": "xxx",
        "xxx": {
            "id": "1",
            "code": "12345",
            "description": "Hello"
        }
    }
}

I want to deserialize it to custom C# classes. "data" is an object that contains two specific pairs: value of the first pair always will be a key for the second pair (e.g., { "name": "yyy", "yyy": "some_object"}). I've added two classes but I don't know how to set name for "DataResponse" property, because it's not static:

public sealed class Data
{
    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }

    [JsonProperty(PropertyName = "xxx")]
    public DataResponse DataResponse { get; set; }
}

public sealed class DataResponse
{
    [JsonProperty(PropertyName = "id")]
    public int Id { get; set; }

    [JsonProperty(PropertyName = "code")]
    public string Code { get; set; }

    [JsonProperty(PropertyName = "description")]
    public string Description { get; set; }
}

Main code to deserialize this JSON:

Data data = JsonConvert.DeserializeObject<Data>(json);
// do some stuff with data...

Upvotes: 0

Views: 909

Answers (1)

dbc
dbc

Reputation: 116786

You can do this with a simple JsonConverter. For instance, if we make your Data class be a generic class supporting any object value type, you would do:

public sealed class Root<T>
{
    public Data<T> data { get; set; }
}

[JsonConverter(typeof(GenericDataResponseConverter))]
public sealed class Data<T>
{
    public string Name { get; set; }

    public T DataResponse { get; set; }
}

class GenericDataResponseConverter : JsonConverter
{
    Type GetDataResponseType(Type objectType)
    {
        return (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Data<>) ? objectType.GetGenericArguments()[0] : null);
    }

    public override bool CanConvert(Type objectType)
    {
        return GetDataResponseType(objectType) != null;
    }

    object ReadJsonGeneric<T>(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var data = ((Data<T>)existingValue) ?? new Data<T>();
        var obj = JObject.Load(reader);
        data.Name = (string)obj["name"];
        data.DataResponse = obj[data.Name].ToObject<T>(serializer);
        return data;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var method = GetType().GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
        var genericMethod = method.MakeGenericMethod(new[] { GetDataResponseType(objectType ) });
        return genericMethod.Invoke(this, new object[] { reader, objectType, existingValue, serializer });
    }

    void WriteJsonGeneric<T>(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var data = (Data<T>)value;
        var dict = new Dictionary<string, object> 
        {
            { "name", data.Name },
            { data.Name, data.DataResponse },
        };
        serializer.Serialize(writer, dict);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var method = GetType().GetMethod("WriteJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
        var genericMethod = method.MakeGenericMethod(new[] { GetDataResponseType(value.GetType()) });
        genericMethod.Invoke(this, new object[] { writer, value, serializer });
    }
}

Then, use it like:

        var root = JsonConvert.DeserializeObject<Root<DataResponse>>(jsonString);
        Debug.WriteLine(JsonConvert.SerializeObject(root, Formatting.Indented));

If you don't need the generic DataResponse type, it becomes even easier:

public sealed class Root
{
    public Data data { get; set; }
}

[JsonConverter(typeof(DataResponseConverter))]
public sealed class Data
{
    public string Name { get; set; }

    public DataResponse DataResponse { get; set; }
}

class DataResponseConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Data);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var data = ((Data)existingValue) ?? new Data();
        var obj = JObject.Load(reader);
        data.Name = (string)obj["name"];
        data.DataResponse = obj[data.Name].ToObject<DataResponse>(serializer);
        return data;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var data = (Data)value;
        var dict = new Dictionary<string, object> 
        {
            { "name", data.Name },
            { data.Name, data.DataResponse },
        };
        serializer.Serialize(writer, dict);
    }
}

Upvotes: 1

Related Questions