I'm working on an ASP .NET core web api where I'm trying to create a model in C# based on a JSON that can have dynamic properties.
The type field can be of 3 types: Land, Water, Air.
Based on the type, the corresponding properties in the "source" would be different.
For example. if the type is "Land", there would be an additional property in source called "speed". If the type is "Air", there would be 2 additional properties in source called "Height" and "NumberLandings".
The modify property is an array that can have different types of modifications - Performance/Aesthetic/Functional...., and based on the modification type, I can have different properties underneath for that modification.
For example, if the type is "Performance", there would be additional properties such as brakes/turbo/suspension.
{
"properties": {
"source": {
"type": "Land",
"speed": "160mph"
},
"modify ": [
{
"type": "Performance",
"brakes": "",
"turbo": "",
"suspension": ""
},
{
"type": "Functional",
"electric": "",
"applications": "Carplay"
}
]
}
}
Question: How can I construct my C# class/model for the dynamic JSON? I'm thinking to keep the source type and modification type as an enum, and based on the enum type, define the other parameters.
I don't want to define all the properties and end up having some of them as null, like below:
[DataContract]
public class Source
{
[DataMember]
[JsonProperty(PropertyName = "type")]
public string Type { get; set; }
[DataMember]
[JsonProperty(PropertyName = "speed")]
public string Speed{ get; set; }
[DataMember]
[JsonProperty(PropertyName = "height")]
public string Height{ get; set; }
[DataMember]
[JsonProperty(PropertyName = "NumberLandings")]
public string NumberLandings { get; set; }
}
The type field can be of 3 types: Land, Water, Air. Based on the type, the corresponding properties in the "source" would be different.
This is essentially polymorhism, and there are existing questions and articles regarding this. This might require a custom jsonConverter class, and it might be easier if you format your message to have a separate object, rather than loose properties, i.e:
"type": "Land",
"MyBaseObject":{
"speed": "160mph"
}
The modify property is an array that can have different types of modifications - Performance/Aesthetic/Functional...., and based on the modification type, I can have different properties underneath for that modification.
This sounds like a key-value store, and may use the same approach to polymorphism as above to store different properties for different kinds of objects, i.e. it could be represented by a Dictionary<string, MyModificationBase>.
Keep in mind that you still need to use the object model somehow, preferably without needing do typechecking all over the place.
Related
Let's say I have this little json snippet:
{
"Type": "Bar",
"BarOnly": "This is a string readable when deserialized to the Bar class only, as declared in my type key"
}
I also have these three classes:
public class Base
{
public enum SampleEnum
{
Bar,
Baz,
}
public SampleEnum Type
{
get;
set;
}
}
public class Bar : Base
{
public string BarOnly
{
get;
set;
}
}
public class Baz : Base
{
public string BazOnly
{
get;
set;
}
}
Based on the Type property in the json snippet, I'd like to have it deserialize to either Bar or Baz.
My first idea was to first deserialize it to the Base class, and then use its type and a switch statement to deserialize the JSON again to its respective class. (Using Newtonsoft.Json)
var type = JsonConvert.DeserializeObject<Base>(json).Type;
string message = "";
switch (type)
{
case (Base.SampleEnum.Bar):
message = JsonConvert.DeserializeObject<Bar>(json).BarOnly;
break;
case (Base.SampleEnum.Baz):
message = JsonConvert.DeserializeObject<Baz>(json).BazOnly;
break;
}
Console.WriteLine(message);
Needless to say that this process is extremely redundant, tedious and, since the switch statement is hard-coded, not very "dynamic" at all.
Another idea was to use a generic class as the base class instead and passing in the type of the actual class it should deserialize to, but then I end up with the same switch statement to figure out what that class should be.
Since you can't map enums to class types, I also thought about using a Dictionary to map the possible enum values to their class counterparts; this still makes the mapping process hard-coded though.
Is there any way I can dynamically get the corresponding class to deserialize to based on the type property of the json object?
EDIT: There seems to be some confusion about how this is supposed to be used and how the data is fetched; let me provide some background information.
I'm iterating through a directory with a lot of different spreadsheet files, mostly CSVs and XML files. Each of these feeds have a "meta file", describing how to process their content. This includes checksums, delimiters and other information. They also declare of what type their parent file is (CSV, XML etc). Hence, they share a lot of common properties (like the Base class in my example), but also have their own set of properties. They derive from an abstract class that requires them to implement a function that returns an instance of the corresponding feed processing class, initialized with values directly from within the meta class. I hope this makes sense.
#OguzOzgul commenting is correct. I've done this countless of times for objects that are composed with interfaces that need to be serialized and deserialized.
See TypeNameHandling for Newtonsoft:
https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm
Your json file will look ever so slightly different:
{
"$type": "SomeNamespace.Bar",
"BarOnly": "This is a string readable when deserialized to the Bar class only, as declared in my type key"
}
If you use
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
}
During serialization, it will add the full type name of all objects to make sure newtonsoft knows what their type is during deserialization (given you use the same settings then). That way you do not have to write your own custom code for type detection.
I have a scenario where I receive json as a string. Each piece of json is formatted the same, except for a collection of items within. Take this as simplified examples:
{
"Type": "A",
"Items": [
{ "Name": "Item 1" },
{ "Name": "Item 2" }
]
}
{
"Type": "B",
"Items": [
{ "Counter": 1 },
{ "Counter": 2 }
]
}
I have the following models in code to represent this:
public enum Type
{
A, B
}
public class InstanceTypeA
{
public string Name { get; set; }
}
public class InstanceTypeB
{
public int Counter { get; set; }
}
note: in the future, the types will be extended further, say to D, E, and F for this example. All of which will have an item collection structured differently again.
And I would like to deserialize this into a model, for example:
public class Container<T>
{
public Type Type { get; set; }
public List<T> Items { get; set; }
}
where T would be either instanceTypeA or instanceTypeB
As mentioned, I receive the json shown at the top as a string. And it's the "type" enum that dictates the type of the object held in the items collection. I'm struggling to find a very generic way to implement this serialization.
I was thinking the factory pattern could help but not sure how to implement this - has anyone tackled a similar problem.
In my code, I happen to know if it's type A, B etc before actually attempting serialization. I could do this:
switch(obj.Type)
{
case A: JsonConvert.DeserializeObject<Container<A>>(obj.Json)
case B: JsonConvert.DeserializeObject<Container<B>>(obj.Json)
}
The drawback of this approach is that the calling code needs to then know its dealing with Container<A> or Container<B> etc. Which isn't generic at all. I can't get A or B to implement a common interface as they are both completely different. Bit stumped with this - I was hoping Factory pattern could help but can't see how - has anyone dealt with a similar issue before?
Thanks for any pointers in advance!
You don't want to use the Type property to dictate what type is used. When using the Newtonsoft Json serializer, you can include the type of an object in the JSON document. That way, when the serializer deserializes the string, it will understand what types to use. So you could completely drop the Type property and just check the type of object in the array. I believe you need to set the TypeNameHandling property of the settings object passed into the serializer. Once you see the serializer outputting a $type property with your objects in the JSON string, you know that you've done it right.
I am serializing objects to json using Json.NET. However, certain properties of my object are objects themselves that contain quite a lot of additional information, so my json output is very cluttered and hard to interpret. Ideally, I would like to show that these specific properties exist (show the object type) but without fully serializing them.
For example, instead of:
{
"sport": "football",
"popular": true,
"rules": [
{
"highSchool":
{
...
}
},
{
"college":
{
...
}
},
{
"nfl":
{
...
}
}
]
}
I would like to show:
{
"sport": "football",
"popular": true,
"rules": {Sports.Football.Rules}
}
Is this possible? I have tried using [JsonIgnore] in my Rules class for the rules property, but this ignores the rules property entirely instead of including but not serializing the property's object. I also read that [JsonProperty] and [JsonIgnore] are static and cannot be conditionally set, so that seems to rule out an alternative approach of selectively serializing the property (such as if Verbose is provided).
I am using Visual Studio's auto generated REST Web Application project structure to create a RESTful API that consumes a JSON input.
Without going into too much detail, I have attempted to define a JSON structure like these:
Example 1:
"operation": {
"type": "EXIST",
"args": [
{
"ColumnName": "SomeColumnA",
"Row": 2
}
]
}
Example 2:
"operation": {
"type": "ADD",
"args": [
{
"type": "ADD",
"args": [
{
"columnName": "SomeColumnB",
"row": 12
},
{
"columnName": "SomeColumnC",
"row": 18
}
]
},
20
]
}
operation represents one of any number of basic database operations and the arguments for those operations. In my first example, the operation is EXIST, which should check a database cell to see if a value exists or not. The args for this operation is simply an object that contains the column and row information for the cell to check (I call this a Value). In my second example, the function is ADD, which should add two values together and return the sum. Here, the arguments are a constant of 20 and a nested ADD function, which itself takes two Values. So, in general, the args array can take either primitive values, another nested operation, or a pair of values that represents a cell to read the actual value from. The ultimate goal here is to create a general structure that would allow me to nest combinations of functions, cell values, and constants to create compound functions like Average or Sum.
In my Models folder, I have the following classes to cast my data to:
public class Instruction
{
public Operation[] Operations { get; set; }
}
public class Operation
{
public string Type { get; set; }
public object[] Args { get; set; }
}
public class Value
{
public string ColumnName { get; set; }
public int Row { get; set; }
}
Note that the Args in Operation is of type object[].
When I call my web application by POSTing this JSON to it, C# automatically parses the JSON into objects defined in my Models folder. Say we used Example 1:
[HttpPost]
public IHttpActionResult Foo(Instruction instruction)
{
foreach(Operation op in instruction.Operations) {
switch (op.Type) {
case "EXIST":
Console.WriteLine("exist"); // works fine
// since we got here, expect Args[0] to be type 'Value'
var value = (Value) op.Args[0]; // InvalidCastException
// logic for EXIST
case "ADD":
// logic for ADD
// ...
}
}
}
It's casting Operation just fine, and I get Type out correctly. I also get Args as an object[] with a lone element in it. But if I try to cast it to Value, it refuses to cast properly.
My question after all of this is: what is the best way to achieve what it looks like I'm attempting to do here? Am I on the right track? If so, what is my error? If I'm going about this the wrong way, what is a better practice alternative? Note that since I'm using Visual Studio's out-of-the-box Web Application framework I don't seem to have access to the function that deseralizes the JSON, so I don't think I can build a custom deserializer.
See this fiddle that shows how I would use the dynamic type.
Fiddle
public static void Main()
{
var json = #"{""operation"": {
""type"": ""ADD"",
""args"": [
{
""type"": ""ADD"",
""args"": [
{
""columnName"": ""SomeColumnB"",
""row"": 12
},
{
""columnName"": ""SomeColumnC"",
""row"": 18
}
]
}
]
}}";
dynamic data = JsonConvert.DeserializeObject(json);
Console.WriteLine(data.operation.type.ToString());
Console.WriteLine(data.operation.args[0].args[0].columnName.ToString());
Console.WriteLine((int)data.operation.args[0].args[0].row);
}
I want to access some address of pictures in a JSON, but the field name is a number and in c# a number is not a valid name for a variable...
My JSON:
{
"id":3441,
"name":"test",
"address": {
"1":"url.com\/45.jpg",
"2":"url.com\/23.jpg",
"3":"url.com\/65.jpg",
"4":"url.com\/789.jpg",
},
"count":2
}
My code in C#: (HrmlResult is my JSON)
dynamic stuff1 = Newtonsoft.Json.JsonConvert.DeserializeObject(HtmlResult);
string address= stuff1.address; //It Works
string allPics = stuff1.pic; //It Works
firstPicTextBox.Text= stuff1.pic.1; //compiler Error
secondPicTextBox.Text = stuff1.pic[2]; //runtime Error
What should I do?!
Thank you all...
You have to create Model object with properties, as in found json you're expecting.
Then, for number properties, you can use JsonProperty attribute to name the property as number, for example:
class MyModel {
[JsonProperty("2")]
public string Two {get; set;}
}
and then use DeserializeObject<MyModel> version
This is simplified example, for your object you have to maintain the hierarchy and probably have another class for 'address' property and use it as property type in the main model.
If you're able to modify the JSON, that's your best bet. What you have seems like it should be in an array:
{
"id":3441,
"name":"test",
"address":[
"url.com\/45.jpg",
"url.com\/23.jpg",
"url.com\/65.jpg",
"url.com\/789.jpg"
],
"count":2
}
From there, your address is a simple array of strings, rather than a numbered key-value pair collection.