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?