I have been battling with this for few hours and I can't figure out a solution.
Using JSON.NET, I'm trying to deserialize some data to one or another derived class, BUT I want to target the right derived class based on a field that actually is in those data...
Here is a simplified example :
public class BaseFile {
public string Name{get;set;}
}
public class Directory : BaseFile {
public int FileSize {get;set;}
}
public class Video : BaseFile {
public int Duration{get;set}
}
I receive those JSON formatted data :
{
"files": [
{
"content_type": "application/x-directory",
"size": 566686478
},
{
"content_type": "video/x-matroska",
"duration": 50
}
}
Now, I want to use JSON.NET, based on the content_type field, to instantiate either a Directory object (if the content_type is application/x-directory) or a Video object (if the content_type is video/x-matroska).
The simple solution is to deserialize all the content to the base class, and then transform those into their respective derived classes, but I don't find this effective so I'm wondering if there's another solution out there !
Thank you in advance for your input(s).
A friend of mine pointed me this post that solves my problem :
Deserializing heterogenous JSON array into covariant List<> using JSON.NET
I just tried it and for my case, the adapted code is written like this :
private BaseFile Create(Type objectType, JObject jObject)
{
var type = (string)jObject.Property("content_type");
switch (type)
{
case "application/x-directory":
return new Directory();
case "video/x-matroska":
return new Video();
default:
return new BaseFile();
}
}
Related
I've been having trouble finding a question about my particular case:
I need to deserialize the following JSON:
{
"name": "My Farm",
"barns": [
{
"name"": "Barn A"",
"animalTypes": [
"Cow",
"Goat"
]
}
]
}
to the following code model:
public class Farm
{
public string name;
public Barn[] barns;
}
public class Barn
{
public string name;
public AnimalType[] animalTypes;
}
public class AnimalType
{
public int typeID;
}
The problem: I need to deserialize a JSON string (which described an animal species name) into an 'AnimalType' object which contains a "type ID" int. This requirement is not something I can change.
To get the animal's integer type ID, I have access to an externally supplied "AnimalTypeResolver" class, which looks like this:
public class AnimalTypeResolver
{
public int GetAnimalType(string animalTypeName)
{
// queries a map to return the right ID.
}
}
I can query that resolver to get the int value I need to store for each animal type.
So, I tried to write a custom JSONConverter for AnimalType:
public class AnimalTypeConverter : JsonConverter<AnimalType>
{
public AnimalTypeResolver animalTypeResolver;
public AnimalTypeConverter(AnimalTypeResolver animalTypeResolver)
{
this.animalTypeResolver = animalTypeResolver;
}
public override bool CanWrite => false;
public override AnimalType ReadJson(ref JsonReader reader, Type objectType, AnimalType existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
string animalTypeName = (string)reader.Value;
return new AnimalType
{
typeID = animalTypeResolver.GetAnimalType(animalTypeName)
};
}
return null;
}
public override void WriteJson(JsonWriter writer, AnimalType value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And my deserialization code looks like this:
string farmJSON =
#"{
""name"": ""My Farm"",
""barns"": [
{
""name"": ""Barn A"",
""animalTypes"": [
""Cow"",
""Goat""
]
}
]
}";
JsonSerializer serializer = new JsonSerializer();
serializer.Converters.Add(new AnimalTypeConverter(animalTypeResolver)); // this animalTypeResolver is supplied from elsewhere in code.
Farm farm = JsonConvert.DeserializeObject<Farm>(farmJSON);
But I get a runtime error:
ArgumentException: Could not cast or convert from System.String to AnimalType.
From reading similar questions, I believe my problem is that I'm trying to deserialize a nested field of a custom type (AnimalType[]). Other answers have explained that JToken.FromObject() creates a new JsonSerializer for each level of deserialization, which has no concept of the JsonConverters I added to a higher-level serializer.
However, For one reason or another, the other questions on this site have answers which aren't exactly applicable to my case.
How I can use my custom JsonConverter, to handle a case in which the data is deeply nested?
If anyone can offer advice about how to make this work, thank you!
JsonConvert.DeserializeObject<Farm>(farmJSON) does not use your JsonSerializer instance. I mean, how could it possibly access the serializer variable that you created and assigned in your code?
You have two choices: Either use the Deserialize method of the serializer instance you did just setup to deserialize your json data and not use JsonConvert.DeserializeObject .
Or instead of setting up a serializer, define some JsonSerializerSettings with your custom JsonConverter and pass those settings to the JsonConvert.DeserializeObject method. Alternatively you could instead also define default serialization settings for JsonConvert.DeserializeObject as demonstrated here in the Newtonsoft.Json documentation: https://www.newtonsoft.com/json/help/html/DefaultSettings.htm
I'm looking to construct a C# object to produce the following JSON array:
["Key":"SomeKeyValue", "Key":"SomeKeyValue"]
Using a list gives me the array I'm after, but with a object name in the resulting JSON:
Public Class SomeOtherClass
{
public string Key {get; set;}
}
Public Class SomeObject
{
public List<SomeOtherClass> PropertyName { get; set; }
}
// JSON
{"PropertyName":[ {"Key":"SomeValue"} ] }
I need to drop the name of the object in the resulting JSON. Using a Dictionary<string,string> gives me what I'm after but with the Object syntax {} in the JSON instead if the Array []:
{
...
}
Trying to avoid custom serializer and contracts here if possible. I'm sure that NewtonSoft must have some magic attribute here?
Found a few related questions on the net, but none that resolves removing the property name. Any suggestions?
From looking at a XML to JSON converter, the conversion from the XML:
<ClassName>
<!--Zero or more repetitions:-->
<key>value1</key>
<key>value2</key>
<key>value3</key>
</ClassName>
has the JSON equivalent of:
"ClassName": {
"key": [
"value1",
"value2",
"value3"
]
}
Using this for now until I can test it on more 3rd party systems that I need to integrate with.
I have a converter class that receives json in input, here are 2 valid examples:
{
"method": "Model",
"payload": {
"key": "value"
}
}
and
{
"method": "OtherModel",
"payload": {
"foo": "bar"
}
}
In C#, I have classes mapped to each possible model:
public class Model
{
public string Key { get; set; }
}
public class OtherModel
{
public string Foo { get; set; }
}
I need a generic converter
How can I use the string value in the method of the JSON to convert in a generic way the content of the payload field?
Is using a huge switch the only way? This is the prototype I have so far but there are hundreds of different models so it will grow quite large...
public IResult ParseJson(string json)
{
Regex regexMessageName = new Regex("\"messageName\": \"(.*?)\"", RegexOptions.Compiled);
var messageName = regexMessageName.Match(json).Groups[1].Value;
switch (messageName)
{
case "Model":
var raw = JsonConvert.DeserializeObject<JsonData<Model>>(json);
return new LogInfoRequestResult<Model> { Raw = raw };
case "OtherModel":
var raw = JsonConvert.DeserializeObject<JsonData<OtherModel>>(json);
return new LogInfoRequestResult<OtherModel> { Raw = raw };
}
}
If you want complete control of your classes, and allow them to evolve independently, then you can have one base class that owns the Method, and then as many subclasses as you want with their own definition of the payload.
First, parse into the baseclass, just to get a strongly typed deserialization of Method
Then, there are a lot of patterns to address branching logic.
If you have 1-2 cases, an if statement is fine
If you have 3-5 cases, you can use a switch
If you have 6-10 cases, you can create a dictionary that maps method name to class type
If you have more than that, you can use the strategy pattern and pass an interface around
Here's an example of how you could write the code:
var json = #"{
'method': 'Model',
'payload': {
'key': 'value'
}
}";
var modelBase = JsonConvert.DeserializeObject<ModelBase>(json);
var methodMapping = new Dictionary<string, Type>()
{
{MethodTypes.Model.ToString(), typeof(Model)},
{MethodTypes.OtherModel.ToString(), typeof(OtherModel)},
};
Type methodClass = methodMapping[modelBase.Method];
var result = JsonConvert.DeserializeObject(json, methodClass);
Note: Since we're programmatically determining the correct type, it's hard to pass to a generic <T>, so this uses the overload of DeserializeObject that takes type as a param
And here are the classes that model incoming messages
public enum MethodTypes
{
Model,
OtherModel
}
public class ModelBase
{
public string Method { get; set; }
}
public class Model : ModelBase
{
public ModelInfo Payload { get; set; }
public class ModelInfo
{
public string Key { get; set; }
}
}
public class OtherModel : ModelBase
{
public ModelInfo Payload { get; set; }
public class ModelInfo
{
public string Foo { get; set; }
}
}
Dictionary<string,string>
If your data is always going to be "foo":"bar" or "key":"value" .... string:string, then Cid's suggesting to use Dictionary<string,string> Payload makes a lot of sense. Then figure out however you want to map from that c# class in a c# constructor that returns whatever type you want.
Additional Resources:
How to handle both a single item and an array for the same property using JSON.net
Deserializing polymorphic json classes without type information using json.net
JSON.NET - Conditional Type Deserialization
Conditionally deserialize JSON string or array property to C# object using JSON.NET?
You can instanciate an object of the expected class using Activator.CreateInstance(), then populate it with JsonConvert.PopulateObject()
In example :
Type t = Type.GetType($"NameSpaceName.{messageName}"); // this must be a fully qualified name
object obj = Activator.CreateInstance(t);
JsonConvert.PopulateObject(json, obj);
I am consuming a web service that will calculate tax. The problem is I don't always get the data back in exactly the same formatn (see example below). When I deserialize the data my code throws an exception. Any tips on what I can do to allow the deserialization to handle a single element or an array of a single element? FYI, I get back a lot more data in addition to the tax, but I am only showing the tax part to keep it simple.
Sometimes I get the data like this:
{
"data": {
"tax": [{
"amount": 0.00,
"category": 0.0
}]
}
}
Sometimes I get it like this:
{
"data": {
"tax": {
"amount": 336.01,
"category": 0.0
}
}
}
Here is my class:
public class Tax
{
public float amount { get; set; }
public float category{ get; set; }
}
I am thinking about adding an [XmlIgnore] attribute and manually deserailizing to get the tax data, but I would like to stay away from that if possible.
Not sure how XmlIgnore would help with your JSON serialization, but i would suggest using Newtonsoft.Json to deserialize your payload to JObject. Then you can use Linq to JSON to investigate the result and perhaps manually instantiate your own object based on the type of "tax" property (JArray or JObject)
see LINQ to JSON for more info.
Make two (or more) different classes, then use the one that doesn't throw an exception when you deseralize.
It looks like you could deseralize the array data using this class:
public class data
{
public Dictionary<string, double> tax { get; set; }
}
If that class successfully deserializes, you can then copy it over to the tax class, either manually, or by using Reflection.
I'd use JSON.Net (link to nuget). Then I'd get a JObject from JObject.Parse method and check whether the relevant child element is JObject or JArray. Then, I'd convert it to your data class (or a dynamic type)
dynamic taxData;
var json = JObject.Parse(response);
var rawTaxData = json["data"]["tax"];
if (rawTaxData is JObject)
{
taxData = JsonConvert.DeserializeObject(rawTaxData);
}
else if (rawTaxData is JArray)
{
var actualData = rawTaxData [0];
taxData = JsonConvert.DeserializeObject(actualData);
}
Also, just to be sure that your server actually returned data and not, for example, error message, use TryGetValue:
JToken dataToken;
if (!json.TryGetValue("data", out dataToken))
{
var rawTaxData = dataToken["tax"];
// ...
}
I need help or some inspiration for some strange json deserialization.
This is the json I recieve from a service (can't change it, its an external service):
{
"status":"OK",
"statuscode":200,
"payload":{
"solarforecast":{
"5876":{
"2014-06-06 23:00:00":{
"bh":0,
"dh":0
},
"2014-06-07 00:00:00":{
"bh":0,
"dh":0
},
[...]
}
}
}
I made a call to get values for a object with id 5876.
So if I made a call for object with id 1254, the json changed this way:
[...]
"solarforecast":{
"1254":{
"2014-06-06 23:00:00":{
[...]
I now want to create an c# object from this json code with help of Newton ;) .
My frist Problem is that the property name (aka object id) is different for any object call and its a number.
My second problem are the sub objects with undefinded count. I think in a well formed json object it has to be somethink like this (see "[" brackets):
"solarforecast":{
"5876":[
"2014-06-06 23:00:00":{
"bh":0,
"dh":0
},
"2014-06-07 00:00:00":{
"bh":0,
"dh":0
},
[...]
]
}
Has anybody a trick or solution how to well deserialize this json into a proper c# class?
I try to get somethink like that as result:
public class Payload
{
[JsonProperty("solarforecast")]
public SolarForecast SolarForecast;
}
public class SolarForecast
{
[JsonProperty("???")]
public IEnumerable<SolarForecastTimeSet> SomeObjectID;
}
public class SolarForecastTimeSet
{
[JsonProperty("???")]
public decimal TimeStamp;
[JsonProperty("dh")]
public decimal DiffusRadiationHorizontal;
[JsonProperty("bh")]
public decimal DirectRadiationHorizontal;
}
Thanks for your help!!
Steffen
Ok figured out how it will work!
Object tree has to be:
public class Payload
{
[JsonProperty("solarforecast")]
public Dictionary<int, Dictionary<DateTime, SolarForecastTimeSet>> SolarForecast;
}
public class SolarForecastTimeSet
{
[JsonProperty("dh")]
public decimal DiffusRadiationHorizontal;
[JsonProperty("bh")]
public decimal DirectRadiationHorizontal;
}
Thanks #andyp for his dictionary hint!