Sign up ×
Stack Overflow is a community of 4.7 million programmers, just like you, helping each other. Join them; it only takes a minute:

For my current C# project, I am trying to build some sort of dynamic json converter :
I collect data from various APIs. Each API has its own data format, but in the end they all provide the same kind of data. The list of APIs I use may evolve with time, so I am looking for a way to map the returned jsons to a single class without the need of coding each time a new API is used.

Looking on SO, it looks like I must use :

  • a custom JsonConverter
  • a custom ContractResolver, since decorating my class properties with [JsonConverter] attributes is useless in this case (not a single json data format)

So I went for this, using a mapping data, linked to my objects, and passed to the converter.

At the moment obviously I miss something with how Json.net works :

  • for some APIs my custom ReadJson method never gets called
  • for others it gets called but the reader parameter content is not what I expected it to be.

Here is my code sample.

ContractResolver

public class dynamicContractResolver<T> : DefaultContractResolver where T : IComparable<T>, IEquatable<T>
{
    private Dictionary<string, commons.jsonMapping> _mapping;

    protected override string ResolvePropertyName(string propertyName)
    {
        if (_mapping.ContainsKey(propertyName))
        {
            return _mapping[propertyName].jsonProperty.Split(new char[] { '/' }).Last();
        }
        else
        {
            return base.ResolvePropertyName(propertyName);
        }
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (_mapping.ContainsKey(property.UnderlyingName))
        {
            JsonConverter converter = new dynamicJSONConverter<T>(_mapping);
            property.Converter = converter;
            property.MemberConverter = converter;
        }

        return property;
    }
}

JsonConverter

public class dynamicJSONConverter<T> : JsonConverter where T : IComparable<T>, IEquatable<T>
{
    private Dictionary<string, commons.jsonMapping> _mapping;

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(myObject<T>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        ...some code, not important here...
    }
}

my classes

public class myObjectCollection<T> : mongoObject where T : IComparable<T>, IEquatable<T>
{

    public List<myObject<T>> myObjects { get; set; } 
    public Dictionary<string, commons.jsonMapping> mapping { get; set; }

private myObject<T> collectData(CancellationToken ct, string APIURL)
    {

        HttpWebRequest BTCeWebReq = (HttpWebRequest)WebRequest.Create(APIURL);
        myObject<T> preResult = new myObject<T>();
        WebResponse response;

        try
        {
            response = BTCeWebReq.GetResponse(ct); //extended method I found on SO that handles timeout ^^
            Stream datastream = response.GetResponseStream();
            StreamReader reader = new StreamReader(datastream);
            string JSONresponse = reader.ReadToEnd();
            var jsonreader = new JsonTextReader(new StringReader(JSONresponse));

            preResult = JsonConvert.DeserializeObject<myObject<T>>(JSONresponse, new JsonSerializerSettings { ContractResolver = new dynamicContractResolver<T>(mapping) });

...some more code...
        }
    }
}

public class myObject<T> : mongoObject where T : IComparable<T>, IEquatable<T>
{
    public List<mySubObject<T>> A { get; set; }
    public List<mySubObject<T>> B { get; set; }
}

public class mySubObject<T> : mongoObject, IComparable<mySubObject<T>>, IEquatable<mySubObject<T>> where T : IComparable<T>, IEquatable<T>
{
    public T Pvalue { get; set; }
    public T Qvalue { get; set; }
}

public class commons
{
public struct jsonMapping
    {
        public string jsonProperty { get; set; }
        public Jsontokey key { get; set; }
    }
}

So the idea is that I set 1 instance of myObjectCollection per APIs I collect data from. Each instance has its own mapping. Then every call to the API should be converted to a myObject instance and added to the myObjects List thanks to:

JsonConvert.DeserializeObject<myObject<T>>(JSONresponse, new JsonSerializerSettings { ContractResolver = new dynamicContractResolver<T>(mapping) });

Here are the 2 cases I have trouble with :

Case 1

Json string

{
"value1":[[253.76399,0.57003695],
    [253.764,0.15716015],
    [254.36916,0.03481701],
    [254.74,0.2402]]],
"value2":[[251.87,0.11],
    [251.01,0.2274489],
    [251.0,0.39243036],
    [250.61,0.22]]
}

mapping data

{
    {"A", new commons.jsonMapping("value1", commons.Jsontokey.A)},
    {"B", new commons.jsonMapping("value2", commons.Jsontokey.B)}
}

When ReadJSON gets called, the reader parameter only contains the value1 array and has reader.TokenType = JsonToken.StartArray, I though it would contain the complete json string.

Case 2

Json string

{
    "result":"success",
    "data":{
        "now":"1437171631078000",
        "a":[["256.25000","7.763",1437171506],
            ["256.30000","9.997",1437169928]],
        "b":[["256.11854","16.090",1437171627],
            ["255.92000","1.190",1437171628],
            ["255.91000","35.000",1437171589],
            ["255.83712","15.573",1437171066]]
        }
    }
}

mapping data

{
    {"A", new commons.jsonMapping("data/a", commons.Jsontokey.A)},
    {"B", new commons.jsonMapping("data/b", commons.Jsontokey.B)}
}

Here ReadJson never gets called. Looking at json.net source code, i feel like I fail to tell it it has to « look » in data to find a and b. I just cannot figure out how to do it!

So, how do I acheive this? is this the correct way, or should I go for a better way?

share|improve this question

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.