Deserializing descendent objects - c#

I'm communicating with a JSON-based API which I can't change. It always returns a Response object with a varying Result object inside. Typically it looks like this:
{ "ver": "2.0", "result": { "code": 0 } }
For certain commands the Result object is 'grown' by adding extra properties:
{ "ver": "2.0", "result": { "code": 0, "hostName": "sample", "hostPort": 5000 } }
I've used Newtonsoft attributes to define the objects as follows:
internal class RpcResponse
{
[JsonProperty(PropertyName = "ver")]
public string Version { get; set; }
[JsonProperty(PropertyName = "result")]
public RpcResponseResult Result
{
get;
set;
}
internal class RpcResponseResult
{
[JsonProperty(PropertyName = "code")]
public int Code { get; set; }
}
internal class RpcExtendedResponseResult: RpcResponseResult
{
[JsonProperty(PropertyName = "hostName")]
public string HostName { get; set; }
[JsonProperty(PropertyName = "hostPort")]
public int HostPort { get; set; }
But when the Response object is deserialized:
RpcResponse rspResponse = JsonConvert.DeserializeObject<RpcResponse>(rspString);
Its Result property always appears as an RpcResponseResult object, ie. JsonConvert doesn't know to construct it as a RpcExtendedResponseResult object.
Is there some way with Attributes or Converters to reinstate the correct descendent object? I feel like I'm missing something obvious!

It's because the type of the object is RpcResponseResult. The deserializer can only deserialize fields that are declared in the type of the field specified. It can't determine because a class has "hostName" its now an RpcExtendedResponseResult.
If I were doing this, I might make the result a container for all possible fields with default values if needed, and then you can fill another object as needed.
internal class RpcResponseResultContainer
{
[JsonProperty(PropertyName = "code")]
public int Code { get; set; }
[JsonProperty(PropertyName = "hostName")]
private string mHostName = string.Empty;
public string HostName
{
get { return mHostName;}
set { mHostName = value; }
}
[JsonProperty(PropertyName = "hostPort")]
private int mHostPort = -1;
public int HostPort
{
get { return mHostPort;}
set { mHostPort = value;}
}
Then if you really wanted to get your object as you want it, you could do something like this in your container class:
public RpcResponseResult GetActualResponseType()
{
if(HostPort != -1 && !string.IsNullOrEmtpy(HostName))
{
return new RpcExtendedResponseResult() { Code = this.Code, HostName = this.HostName, HostPort = this.HostPort};
}
return new RpcResponseResult() { Code = this.Code };
}

First, credit to Matthew Frontino for providing the only answer which I've accepted.
However I opted not to make a single result container, so here's what I ended up doing.
First I started with this page: How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?
I used the version of JsonCreationConverter provided there by Alain.
I added the CanWrite override as suggested there by Dribbel:
public override bool CanWrite
{
get { return false; }
}
I also added my own helper function to JsonCreationConverter:
protected bool FieldExists(string fieldName, JObject jObject) {
return jObject[fieldName] != null;
}
Then I created my own converter as follows:
class RpcResponseResultConverter : JsonCreationConverter<RpcResponseResult>
{
protected override RpcResponseResult Create(Type objectType, JObject jObject)
{
// determine extended responses
if (FieldExists("hostName", jObject) &&
FieldExists("hostPort", jObject) )
{
return new RpcExtendedResponseResult();
}
//default
return new RpcResponseResult();
}
}
Then I deserialize the top-level class and supply any converters to be used. In this case I only supplied one, which was for the nested class in question:
RpcResponse rspResponse = JsonConvert.DeserializeObject<RpcResponse>(
rspString,
new JsonSerializerSettings {
DateParseHandling = Newtonsoft.Json.DateParseHandling.None,
Converters = new List<JsonConverter>( new JsonConverter[] {
new RpcResponseResultConverter()
})
});
Notes:
Anything not explicitly handled by a converter (such as the top-level class) is deserialized using the default converter built into JsonConvert.
This only works if you can identify a unique set of fields for every descendent class.

Related

Deserializing a JSON array with awkward additional property in the schema

I have a very similar issue to this question here, except my application is in C#, and I can't figure out how to convert the solution unfortunately. I am trying to deserialize a JSON result that looks like this:
"error":[],
"result":
{
"MANAEUR":[
[1619042400,"1.11200","1.13488","1.08341","1.10077","1.09896","58878.56534370",137],
[1619046000,"1.09767","1.12276","1.08490","1.11097","1.10456","25343.25910419",77],
],
"last":1619118000
}
I use the following classes:
public class ResponseBase
{
[JsonProperty(PropertyName = "error")]
public List<string> Error;
}
public class OHLCResponse : ResponseBase
{
[JsonProperty("result")]
public OHLCResult Result;
}
public class OHLCResult
{
[JsonProperty("pair_names")]
public Dictionary<string, OHLC[]> GetHistory;
[JsonProperty("last")]
public long Last;
}
.... and then finally the guts of it:
public class OHLC
{
public int Time;
public decimal Open;
public decimal High;
public decimal Low;
public decimal Close;
public decimal Vwap;
public decimal Volume;
public int Count;
}
I have a standard deserializer class which works for all other calls I am using to the same API, but I cannot get this call to work. When I retrieve OHLCResponse object,I don't get an error, and "Result.Last" is always populated, but the expected array of OHLC items in "Result.GetHistory" is always empty/null. I know that the data has been returned successfully since I can see the data in the variable returned from the WebRequest that I am then passing to the deserializer function, so it must be that I have these classes laid out incorrectly I guess.
Can anyone see what I'm doing wrong?
Many thanks in advance, Dave
The object you posted isn't valid JSON. The outside curly braces are missing. So I am going to assume it should look like this:
{
"error": [],
"result": {
"MANAEUR": [
[1619042400, "1.11200", "1.13488", "1.08341", "1.10077", "1.09896", "58878.56534370", 137],
[1619046000, "1.09767", "1.12276", "1.08490", "1.11097", "1.10456", "25343.25910419", 77],
],
"last": 1619118000
}
}
Anonymous Deserialization
The first method you could do, which may be a tad kludgy since you have to deserialize twice, is use anonymous deserialization.
Let's start by defining some models:
public sealed class OHLCModel
{
public long Time { get; set; }
public decimal Open { get; set; }
public decimal High { get; set; }
public decimal Low { get; set; }
public decimal Close { get; set; }
public decimal Vwap { get; set; }
public decimal Volume { get; set; }
public int Count { get; set; }
}
public sealed class ResultModel
{
[JsonIgnore]
public IEnumerable<OHLCModel> Manaeur { get; set; }
[JsonProperty("last")]
public long Last { get; set; }
}
public sealed class RootModel
{
[JsonProperty("error")]
public List<string> Error { get; set; }
[JsonProperty("result")]
public ResultModel Result { get; set; }
}
As you can see we are ignoring the Manaeur object when serialization happens.
To make this method work, we'd do this:
var json = System.IO.File.ReadAllText(#"c:\users\andy\desktop\test.json");
// First, just grab the object that has the mixed arrays.
// This creates a "template" of the format of the target object
var dto = JsonConvert.DeserializeAnonymousType(json, new
{
Result = new
{
Manaeur = new List<List<object>>()
}
});
// Next, deserialize the rest of it
var fullObject = JsonConvert.DeserializeObject<RootModel>(json);
// transfer the DTO using a Select statement
fullObject.Result.Manaeur = dto.Result.Manaeur.Select(x => new OHLCModel
{
Time = Convert.ToInt64(x[0]),
Open = Convert.ToDecimal(x[1]),
High = Convert.ToDecimal(x[2]),
Low = Convert.ToDecimal(x[3]),
Close = Convert.ToDecimal(x[4]),
Vwap = Convert.ToDecimal(x[5]),
Volume = Convert.ToDecimal(x[6]),
Count = Convert.ToInt32(x[7])
});
This isn't the most ideal solution as you are tightly coupling to the model in a few spots. The ideal way to do this would be to make a custom JsonSerializer.
Use a Custom JsonConverter
First thing we do is change your ResultModel to look like this:
public sealed class ResultModel
{
[JsonConverter(typeof(ManaeurJsonConverter)), JsonProperty("MANAEUR")]
public IEnumerable<OHLCModel> Manaeur { get; set; }
[JsonProperty("last")]
public long Last { get; set; }
}
Then implement a JsonConverter:
public sealed class ManaeurJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => false; // this will never get called
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var lst = JArray.Load(reader).ToObject<List<List<object>>>();
return lst.Select(x => new OHLCModel
{
Time = Convert.ToInt64(x[0]),
Open = Convert.ToDecimal(x[1]),
High = Convert.ToDecimal(x[2]),
Low = Convert.ToDecimal(x[3]),
Close = Convert.ToDecimal(x[4]),
Vwap = Convert.ToDecimal(x[5]),
Volume = Convert.ToDecimal(x[6]),
Count = Convert.ToInt32(x[7])
});
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{ // we don't need to write
throw new NotImplementedException();
}
}
You can then simply call it as so:
var json = System.IO.File.ReadAllText(#"c:\users\andy\desktop\test.json");
var fullObject = JsonConvert.DeserializeObject<RootModel>(json);
Your JSON is not valid, I had to modify it to make it valid JSON (https://jsonformatter.org/). I added the root brackets and removed the comma delimter after the second inner array entry.
Valid JSON:
{
"error":[],
"result":
{
"MANAEUR":[
[1619042400,"1.11200","1.13488","1.08341","1.10077","1.09896","58878.56534370",137],
[1619046000,"1.09767","1.12276","1.08490","1.11097","1.10456","25343.25910419",77]
],
"last":1619118000
}
}
After updating the JSON, I used Visual Studio's 'Paste Special' to generate C# objects from the JSON. The following classes were created.
public class RootObject
{
[JsonProperty("error")]
public object[] Error { get; set; }
[JsonProperty("result")]
public Result Result { get; set; }
}
public class Result
{
[JsonProperty("MANAEUR")]
public object[][] Manaeur { get; set; }
[JsonProperty("last")]
public int Last { get; set; }
}
With the above JSON and classes, I used the following to deserialize the JSON.
string json = "{\"error\":[],\"result\":{\"MANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
var obj = JsonConvert.DeserializeObject<RootObject>(json);
EDIT:
To handle the MANAEUR property where the key label can be different.
Create a JsonConverter...
public class ManaeurConverter : JsonConverter
{
private Dictionary<string, string> propertyMappings { get; set; }
public ManaeurConverter()
{
this.propertyMappings = new Dictionary<string, string>
{
{"NOTMANAEUR","MANAEUR"}
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object instance = Activator.CreateInstance(objectType);
var props = objectType.GetTypeInfo().DeclaredProperties.ToList();
JObject jo = JObject.Load(reader);
foreach (JProperty jp in jo.Properties())
{
if (!propertyMappings.TryGetValue(jp.Name, out var name))
name = jp.Name;
PropertyInfo prop = props.FirstOrDefault(pi =>
pi.CanWrite && pi.GetCustomAttribute<JsonPropertyAttribute>().PropertyName == name);
prop?.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
return instance;
}
public override bool CanConvert(Type objectType)
{
return objectType.GetTypeInfo().IsClass;
}
public override bool CanWrite => false;
}
... Add the JsonConverter attribute to the class...
[JsonConverter(typeof(ManaeurConverter))]
public class Result
{
[JsonProperty("MANAEUR")]
public object[][] Manaeur { get; set; }
[JsonProperty("last")]
public int Last { get; set; }
}
... and parse like so...
string json_Manaeur = "{\"error\":[],\"result\":{\"MANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
string json_Not_Manaeur = "{\"error\":[],\"result\":{\"NOTMANAEUR\":[[1619042400,\"1.11200\",\"1.13488\",\"1.08341\",\"1.10077\",\"1.09896\",\"58878.56534370\",137],[1619046000,\"1.09767\",\"1.12276\",\"1.08490\",\"1.11097\",\"1.10456\",\"25343.25910419\",77]],\"last\":1619118000}}";
var objManaeur = JsonConvert.DeserializeObject<RootObject>(json_Manaeur);
var objNotManaeur = JsonConvert.DeserializeObject<RootObject>(json_Not_Manaeur);

How do deserialize an entire response that could be an array or a single object?

I have a situation where the response from an API can contain either an array or a single item. However, I'm struggling with the deserialization of the responses due to the array response containing another nested object. Here are the different responses that can be returned (sample).
This is the format of the response when a list of items is returned
{
"data": {
"items": [
{
"id": 1
},
{
"id": 2
}
]
}
}
This is the response that gets sent when a single item is returned
{
"data": {
"id": 1
}
}
My initial attempt to standardize the response included creating a custom converter attribute, but the issue there is you cannot pass a generic parameter into it. The code for the ReadJson is below:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if(token["items"]?.Type == JTokenType.Array)
{
return token["items"].ToObject<T>();
}
return new List<T>() { token.ToObject<T>() };
}
Here is the class that represents a response, but I'm getting the error that generics cannot be pass into attributes. After reading into it further, it seems as though this is by design.
public class Response<T>
{
[JsonProperty("version")]
public string Version { get; set; }
[JsonConverter(SingleOrArrayConverter<T>)]
public T Data { get; set; }
[JsonProperty("_links")]
public Links Links { get; set; }
}
Anyone have any other thoughts/solutions to this problem?
Have a few classes like below:
public class Response
{
[JsonProperty("data")]
public Data ResponseData { get; set; }
}
public class Data
{
[JsonProperty("id",NullValueHandling = NullValueHandling.Ignore)]
public long? Id { get; set; }
[JsonProperty("items", NullValueHandling = NullValueHandling.Ignore)]
public List<Item> Items { get; set; }
}
public class Item
{
public long? Id { get; set; }
}
Then Deserialize the response like below:
var responseObject = Newtonsoft.Json.JsonConvert.DeserializeObject<Response>(responseString);
To further improve accessing the elements of data, include the below property to the Data class:
public List<Item> ResponseItems
=> Id != null
? new List<Item>(new Item[] { new Item { Id = Id} })
: Items;

JsonConvert string to Collection

I get this json format from an API:
"url_service": "",
"Description": null,
"Target": "5,6",
"Category ": "2"
I'm trying to deserialize the json into a model. The trouble is with the "Target" field which is supposed to be an ICollection of int.
Here is my model:
public string Description{ get; set; }
public ICollection<int> Target{ get; set; }
public int Category { get; set; }
Is there a way to process the Json before it gets serialised in order to make a collection out of the comma separated string?
Instead of trying to change the deserialization logic, why not make it easier on yourself and just include a new property in your model?
public string Description{ get; set; }
public int Category { get; set; }
//Change this back to string, since that is what your JSON is
public string Target{ get; set; }
//Add a "TargetList" property that just reads from "Target" and turns into a List
public List<int> TargetList => Target.Split(',').Select(x => int.Parse(x)).ToList();
Be aware that there is no error handling in my code so you will have to modify accordingly.
Yes, have your C# class implement the ISerializable interface. There are OnDeserialized(), OnSerialized() functions you could implement.
Refer to .NET Deserialisation with OnDeserializing and OnDeserialized
Your JSON fragment does not describe a collection of integers but a string. The correct one would be
"Target": [ 5, 6 ],
which translates into a JSON schema of
"Target": {
"type": ["array"],
"items": { "type": "integer"}
},
If you don't have control of the result then create another property which will be a result of integers such as
private string _target;
public string Target { get { return _target; }
set {
_target = value;
Targets = Regex.Matches(_target, #"(\d+)")
.OfType<Match>()
.Select(mt => int.Parse(mt.Value))
.ToList();
public List<int> Targets { get; set; }
The Target field is of string type in your json, hence the serialiser will attempt to read it as a string.
You can use a converter to overrule that, for example using Newtonsoft Json.
Let's assume your data structure to be defined as follows:
public class Data {
public string Description{ get; set; }
public ICollection<int> Target{ get; set; }
public int Category { get; set; }
}
Then you may create your own JsonConverter as follows:
public class DataConverter : JsonConverter {
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
var jObject = JObject.Load(reader);
var data = new Data();
if (jObject.TryGetValue("Description", out JToken jDescription)) {
data.Description = jDescription.Value<string>();
}
if (jObject.TryGetValue("Target", out JToken jTarget)) {
data.Target = ToTarget(jTarget, serializer);
}
if (jObject.TryGetValue("Category", out JToken jCategory)) {
data.Category = jCategory.Value<int>();
}
return req;
}
private static ICollection<int> ToTarget( JToken jTarget, JsonSerializer serializer ) {
int defaultValue = -1;
var target = new List<int>();
var collection = jTarget.Value<string>().Split(',');
foreach (string str in collection)
if (int.TryParse(str, out int number))
target.Add(number);
else
target.Add(defaultValue);
return target;
}
public override bool CanConvert(Type objectType) => objectType == typeof(Data);
}
You can then deserialise using the following code:
var jsonSettings = new JsonSerializerSettings();
jsonSettings.Converters.Add(new DataConverter);
Data data = JsonConvert.DeserializeObject<Data>(yourJsonString, jsonSettings);
Further considerations: this approach provides clear separation between your data definition and the data parsing logic, hence keeping your data structure and definition clean from any json specific information and parsing logic.

Use different name for serializing and deserializing with Json.Net

I am receiving JSON data from a web API that looks like this:
[
{
"id": 1
"error_message": "An error has occurred!"
}
]
I deserialize this data to objects of the following type:
public class ErrorDetails
{
public int Id { get; set; }
[JsonProperty("error_message")]
public string ErrorMessage { get; set; }
}
Later in my application I would like to serialize the ErrorDetails object again to JSON but using the property name ErrorMessage instead of error_message. So the result would look like this:
[
{
"Id": 1
"ErrorMessage": "An error has occurred!"
}
]
Is there an easy way I can accomplish this with Json.Net? Perhaps using a custom resolver and some attributes like:
public class ErrorDetails
{
public int Id { get; set; }
[SerializeAs("ErrorMessage")]
[DeserializeAs("error_message")]
public string ErrorMessage { get; set; }
}
But the resolver doesn't tell me when I'm serializing or deserializing.
You can make use of the JsonSerializerSettings, the ContractResolver and the NamingStrategy.
public class ErrorDetails
{
public int Id { get; set; }
public string ErrorMessage { get; set; }
}
var json = "{'Id': 1,'error_message': 'An error has occurred!'}";
For dezerialization you could use the SnakeCaseNamingStrategy.
var dezerializerSettings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
}
};
var obj = JsonConvert.DeserializeObject<ErrorDetails>(json, dezerializerSettings);
To serialize the object again you dont have to change the JsonSerializerSettings as the default will use the property name.
var jsonNew = JsonConvert.SerializeObject(obj);
jsonNew = "{'Id': 1,'ErrorMessage': 'An error has occurred!'}"
Or you could create a contract resolver which can decide which name to use. Then you can decide when you dezerialize and serialize if you want to use the pascal case name format or the one with the underscore.
public class CustomContractResolver : DefaultContractResolver
{
public bool UseJsonPropertyName { get; }
public CustomContractResolver(bool useJsonPropertyName)
{
UseJsonPropertyName = useJsonPropertyName;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (!UseJsonPropertyName)
property.PropertyName = property.UnderlyingName;
return property;
}
}
public class ErrorDetails
{
public int Id { get; set; }
[JsonProperty("error_message")]
public string ErrorMessage { get; set; }
}
var json = "{'Id': 1,'error_message': 'An error has occurred!'}";
var serializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CustomContractResolver(false)
};
var dezerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CustomContractResolver(true)
};
var obj = JsonConvert.DeserializeObject<ErrorDetails>(json, dezerializerSettings);
var jsonNew = JsonConvert.SerializeObject(obj, serializerSettings);
jsonNew = "{'Id': 1,'ErrorMessage': 'An error has occurred!'}"
Another way of achieving a different property name when serialising vs deserisalising is by using the ShouldSerialize method: https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm#ShouldSerialize
The docs say:
To conditionally serialize a property, add a method that returns
boolean with the same name as the property and then prefix the method
name with ShouldSerialize. The result of the method determines whether
the property is serialized. If the method returns true then the
property will be serialized, if it returns false then the property
will be skipped.
E.g:
public class ErrorDetails
{
public int Id { get; set; }
// This will deserialise the `error_message` property from the incoming json into the `GetErrorMessage` property
[JsonProperty("error_message")]
public string GetErrorMessage { get; set; }
// If this method returns false then the property after the `ShouldSerialize` prefix will not be serialised into the output
public bool ShouldSerializeGetErrorMessage() => false;
// The serialised output will return `ErrorMessage` with the value from `GetErrorMessage` i.e. `error_message` in the original json
public string ErrorMessage { get { return GetErrorMessage; } }
}
This results in slightly more overhead so be careful if dealing with lots of properties or with lots of data but for small payloads, and if you don't mind messing up your DTO class a little, then this could be a quicker solution than writing custom contract resolvers etc.
I liked the answer by #lee_mcmullen, and implemented it in my own code. Now I think I've found a slightly neater version.
public class ErrorDetails
{
public int Id { get; set; }
// This will deserialise the `error_message` property from the incoming json and store it in the new `GetErrorMessage` property
[JsonProperty("error_message")]
public string GetErrorMessage { get { return ErrorMessage; } set { ErrorMessage = value; } }
// If this method returns false then the property after the `ShouldSerialize` prefix will not be serialised into the output
public bool ShouldSerializeGetErrorMessage() => false;
// The serialised output will return `ErrorMessage` with the value set from `GetErrorMessage` i.e. `error_message` in the original json
public string ErrorMessage { get; set; }
}
The reason I like this better is that in more complicated models it allows for inheritance while keeping all of the "old" custom stuff separate
public class ErrorDetails
{
public int Id { get; set; }
public string ErrorMessage { get; set; }
}
// This is our old ErrorDetails that hopefully we can delete one day
public class OldErrorDetails : ErrorDetails
{
// This will deserialise the `error_message` property from the incoming json and store it in the new `GetErrorMessage` property
[JsonProperty("error_message")]
public string GetErrorMessage { get { return ErrorMessage; } set { ErrorMessage = value; } }
// If this method returns false then the property after the `ShouldSerialize` prefix will not be serialised into the output
public bool ShouldSerializeGetErrorMessage() => false;
}

Deserialize Json into object and child object for return

Not too familiar with JSON yet and have come across a an issue that not obvious to me.
The api I'm querying returns a standard response object with the result of the processed command/api request embedded within a data object within the json.
So the response comes back like the following for all requests on the API the data component changes dependent on whats been requested.
ObjectType1 response
{
"data": {
"person" : {
"id" : 21,
"name" : "Json can be annoying at times"
}
},
"message" : "",
"result" : "success"
}
or another request on api will return the following List of
ObjectType2 response
{
"data": {
"1234" : {
"id": 1234,
"title" : "Terminator"
},
"3245" : {
"id" : 3245,
"name" : "Terminator 2"
}
},
"message" : "",
"result" : "success"
}
I would like to have a custom JsonConverter that pulls out the response into an object as such
public class MyResponse {
[JsonProperty(PropertyName = "data")]
public string Data { get; set; }
[JsonProperty(PropertyName = "message")]
public string Message { get; set; }
[JsonProperty(PropertyName = "status")]
public string Status { get; set; }
}
or
public class MyResponse<T> : class T {
public T Data { get; set; }
public string Message { get; set; }
public string Status { get; set; }
}
And then from there i can act on the Status / message within a generic method then return a json string back to the calling method in my library. From which the json string returned can be processed properly according to the request.
Any ideas how to deserialize data's child object back into a string or even better if i passed the method a generic type T how could i deserialize the json into both objects.
EDIT
Posted answer below for those looking to do something similar
Cheers
Thank you to those that offered some help, but I eventually came up with the answer I was looking for that would deserialize my object into a proper type offered via generics.
Here is my MyCustomResponse object
public class MyCustomResponse
{
[JsonProperty(PropertyName = "data")]
public object Data { get; set; }
[JsonProperty(PropertyName = "message")]
public string Message { get; set; }
[JsonProperty(PropertyName = "result")]
public string Result { get; set; }
}
The custom JsonConverter ended up like so, I look for the property in the json string "data" then convert it to an object of type T
public class MyCustomResponseConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(MyCustomResponse));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object instance = objectType.GetConstructor(Type.EmptyTypes).Invoke(null);
PropertyInfo[] props = objectType.GetProperties();
JObject jo = JObject.Load(reader);
foreach ( JProperty jp in jo.Properties() )
{
PropertyInfo prop = props.FirstOrDefault(pi =>
pi.CanWrite && string.Equals(pi.Name, jp.Name, StringComparison.OrdinalIgnoreCase));
if ( prop != null )
{
// Convert data object to what was passed in at T
if ( jp.Name == "data" )
prop.SetValue(instance, jo.SelectToken("data").ToObject(typeof(T)));
else
prop.SetValue(instance, jp.Value.ToObject(prop.PropertyType, serializer));
}
}
return instance;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use the above I created a Generic method that looks like the following and allows me to pass the command to run, extra query string and also the Type to convert 'Data' object to:
private async Task<T> GenericApiRequestAsync<T>(string command, string query)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Uri uri = new Uri(string.Format("{0}/api/{1}/?cmd={2}{3}", apiUrl, apiKey, command, query));
try {
HttpResponseMessage response = await client.GetAsync(uri);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
// Convert responseContent via MyCustomResponseConverter
var myCustomResponse =
await Task.Factory.StartNew(() =>
JsonConvert.DeserializeObject<MyCustomResponse(
responseContent,
new MyCustomResponseConverter<T>()
));
return (T)myCustomResponse.Data;
}
catch(Exception ex)
{
...
}
}
Then to use the actual GenericApiRequestAsync method i simply pass it the command, query and type for the Data object to be converted into, whatever it maybe.
public async Task<Person> GetPersonAsync(int id)
{
return await GenericApiRequestAsync<Person>("person.byid", string.Format("&id={0}", id));
}
public async Task<IDictionary<string, ObjectType2>> GetObjectType2ListAsync(string name)
{
return await GenericApiRequestAsync<IDictionary<string, ObjectType2>>("show.byname", string.Format("&name={0}", name));
}
Ended up a simple solution but complex in getting there. It removes the need to process the data object a second time into in the final object as well.
Hope this solution can help others out there that come across similar JSON structures, if anyone sees a simpler way to implement the converter, i'm happy to accept any input.
Cheers
For serialization/deserialization of JSON objects, take a look at Json.NET. You can include it as a Nuget package and use built in methods such as JsonConvert.SerializeObject(Object object) and JsonConvert.DeserializeObject(string value, Type type).
You can control the name of the JSON properties by decorating your models with JsonProperty attributes. For example:
public class MyResponse {
[JsonProperty(PropertyName = "data")]
public string Data { get; set; }
[JsonProperty(PropertyName = "message")]
public string Message { get; set; }
[JsonProperty(PropertyName = "status")]
public string Status { get; set; }
}
Two entities:
public class Response
{
public Dictionary<string, Data> data { get; set; }
public string message { get; set; }
public string result { get; set; }
}
public class Data
{
public int id { get; set; }
public string title { get; set; }
}
The response code is:
JavaScriptSerializer serializer = new JavaScriptSerializer();
Response response = (Response) serializer.Deserialize<Response>(jsonString);
As you can see there is no additional packages

Categories

Resources