C# Newtonsoft JSON - Deserializing Object with collection of unknown objects - c#

I'm struggling with deserialization of the json file using the newtonsoft.json. Object which I want to deserialize looks like this:
public class Device
{
public string Name { get; set; }
public int Id { get; set; }
public string Type { get; set; }
public List<Sensor> Sensors { get; }
public bool IsPaired { get; set; }
}
Sensor class is Virtual.
I have multiple classes which inherit from Sensor class (TemperatureSensor, WaterLevelSensor etc.) and add some new properties. Instances of these classes are stored in Sensors collection.
Json file looks like this:
[
{
"Name":"Device1",
"Id":0,
"Type":"TemperatureSensor",
"Sensors":[
{
"Id":0,
"Type":"TemperatureSensor",
"Data":18.136218099999997,
"ReadIntervalInMiliseconds":5000
},
{
"Id":1,
"Type":"TemperatureSensor",
"Data":18.0999819,
"ReadIntervalInMiliseconds":5000
}
],
"IsPaired":false
},
{
"Name":"Device2",
"Id":1,
"Type":"AutomaticGate",
"Sensors":[
{
"OpenPercentage":0,
"Id":0,
"Type":"AutomaticGate",
"Data":0.0,
"ReadIntervalInMiliseconds":0
}
],
"IsPaired":false
},
{
"Name":"Device3",
"Id":2,
"Type":"Other",
"Sensors":[
{
"IsActive":false,
"Id":0,
"Type":"AirConditioner",
"Data":0.0,
"ReadIntervalInMiliseconds":0
},
{
"Id":1,
"Type":"LightSensor",
"Data":4.0,
"ReadIntervalInMiliseconds":5000
}
],
"IsPaired":false
}
]
I assume that i have to read the "Type" of Sensor from json file, and on this basis create the Object and add it to some collection and then return Device class object with this collection.
I was trying to make custom JsonConverter like in this blog post but with little effect.

You can create a custom JsonConverter to convert Sensor objects to concrete derived classes. Here's a working example of such a JsonConverter:
public class SensorConverter : JsonConverter
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType)
{
// Don't do IsAssignableFrom tricks here, because you only know how to convert the abstract class Sensor.
return objectType == typeof(Sensor);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
string sensorType = jObject["Type"].Value<string>();
switch (sensorType)
{
case "TemperatureSensor":
return jObject.ToObject<TemperatureSensor>(serializer);
case "AutomaticGate":
return jObject.ToObject<AutomaticGate>(serializer);
case "AirConditioner":
return jObject.ToObject<AirConditioner>(serializer);
case "LightSensor":
return jObject.ToObject<LightSensor>(serializer);
default:
throw new NotSupportedException($"Sensor type '{sensorType}' is not supported.");
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
Then, when deserializing, you will have to add your custom converter to the settings in order for this to work.
Note that your Sensors property is get-only at the moment. You will have to provide a setter in order for NewtonSoft to populate the property.

Another solution that requires much less code is using JsonSubTypes
Assuming an abstract Sensor class, you need to register via custom attribute a known subclass and it's identifier. So in your case, the identifier is property named "Type" and the class mappings is in KnownSubType attributes.
[JsonConverter(typeof(JsonSubtypes), "Type")]
[JsonSubtypes.KnownSubType(typeof(TemperatureSensor), "TemperatureSensor")]
[JsonSubtypes.KnownSubType(typeof(WaterLevelSensor), "WaterLevelSensor")]
[JsonSubtypes.KnownSubType(typeof(AirConditioner), "AirConditioner")]
[JsonSubtypes.KnownSubType(typeof(AutomaticGate), "AutomaticGate")]
[JsonSubtypes.KnownSubType(typeof(LightSensor), "LightSensor")]
public abstract class Sensor
{
}
In your Device class, Sensors property must have a set property.
public List<Sensor> Sensors { get; set;}
Usage:
var items = JsonConvert.DeserializeObject<List<Device>>(json);

Related

JSON.net TypeNameHandling : customize Auto

I am serializing classes using JSON.net and using TypeNameHandling=Auto to insert the "$type" annotation. For the sake of the example, let's say this is my class:
[JsonObject]
public class MyClass
{
[JsonProperty]
public ISomeInterfaceA MyA { get; set; }
[JsonProperty]
public ISomeInterfaceB MyB { get; set; }
}
With this, both "MyA" and "MyB" receive the "$type" attribute. Unfortunately, I am in a weird situation where I need "MyA" to have "$type" but "MyB" should not have "$type". Does json.net have anything that would allow me to customize the behaviour of TypeNameHandling.Auto so that I can manually choose where I want it or not?
As for why I would want to do such a thing, the complicated reason is that I am migrating to JSON.net from another legacy serializer, and I am attempting to minimize the differences between the old and the new serializer to avoid having to rewrite large amounts of javascript code consuming the JSON.
You can achieve this using a custom converter.
Say this is your class:
[JsonObject]
public class MyClass {
[JsonProperty]
public ISomeInterfaceA MyA { get; set; }
[JsonProperty]
[JsonConverter(typeof(NoTypeConverter))]
public ISomeInterfaceA MyB { get; set; }
}
We need a custom converter:
public class NoTypeConverter: JsonConverter<ISomeInterfaceA> {
public override ISomeInterfaceA ReadJson(JsonReader reader, Type objectType, ISomeInterfaceA existingValue, bool hasExistingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, ISomeInterfaceA value, JsonSerializer serializer)
{
// Note: this is not the most efficient way to do this, but you can customise this how you see fit.
writer.WriteRawValue(JsonConvert.SerializeObject(value));
}
public override bool CanWrite => true;
public override bool CanRead => false;
}
Then:
var stuff = JsonConvert.SerializeObject(new MyClass
{ MyA = new A { Banana = "cheese" },
MyB = new A { Banana = "notype"} },
new JsonSerializerSettings{ TypeNameHandling=TypeNameHandling.Auto });
Console.WriteLine(stuff); // {"MyA":{"$type":"whatever, here","Banana":"cheese"},"MyB":{"Banana":"notype"}}
Note, however, that you can not deserialize BACK to your type this way because the type info is missing for MyB
var x = JsonConvert.DeserializeObject<MyClass>(stuff,
new JsonSerializerSettings{ TypeNameHandling=TypeNameHandling.Auto });
that will throw
Unhandled exception. Newtonsoft.Json.JsonSerializationException: Could not create an instance of type ISomeInterfaceA. Type is an interface or abstract class and cannot be instantiated. Path 'MyB.Banana', line 1, position 69.

How to validate JSON via schema dynamically in C#

I need to validate JSON response dynamically. Schema should depends on value of specific key in key-pair.
Please, note, that I am using JSON.NET 5.0.8 and cannot upgrade to higher version due to compatibility with infrastructre.
If value of "type" is not "SumProperty" ("CountersProperty" in example), then validate "rule" like string:
"property": [
{
"type": "CountersProperty",
"rule": "MEQUAL"
}
]
But! If value of "type" is "SumProperty", then validate "rule" like array (and "rule" should be inside "config"):
"property": [
{
"type": "SumProperty",
"config": {
"rule": [
{
"type": "MEQUAL",
"value": 2
}
]
}
}
]
So I need some kind of dynamic validation, that can "understand" what kind of property we have and validate it appropriately. JSON response can have multiple of "properties" at the same time, so I can't choose one kind of validation or another, it should work dynamically.
You can do this by implementing a custom JsonConverter.
I made the following classes following your sample input
public class Schema
{
[JsonProperty("Property")]
public List<Property> Properties { get; set; }
}
public abstract class Property
{
public string Type { get; set; }
}
public class NotSumProperty : Property
{
public string Rule { get; set; }
}
public class SumProperty : Property
{
public Config Config { get; set; }
}
public class Config
{
[JsonProperty("Rule")]
public List<Rule> Rules { get; set; }
}
public class Rule
{
public string Type { get; set; }
public int Value { get; set; }
}
Then we define our custom JsonConverter. We override the ReadJson() method to implement our conversion clause, in this case, we evaluate the type of the Property.
public class PropertyConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => typeof(Property).IsAssignableFrom(objectType);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
Property p;
switch ((string)obj["type"])
{
case "SumProperty":
p = new SumProperty();
break;
default:
p = new NotSumProperty();
break;
}
serializer.Populate(obj.CreateReader(), p);
return p;
}
}
Finally, here's the usage:
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
};
settings.Converters.Add(new PropertyConverter());
Schema schema = JsonConvert.DeserializeObject<Schema>(json, settings);
Another option, if you don't want to write your own converter, is to deserialize into a a dynamic object, and check these values at run time, as demonstrated here.
This could potentially be more useful if you can't define a clear inheritance patter, though it does rely on clients to implement more parsing/validation logic themselves. In other words, it's a little easier to hit unexpected exceptions - it basically moves potential issues from compile-time errors to run-time errors.

How to exclude specific type from json serialization

I am logging all requests to my WCF web services, including the arguments, to the database. This is the way I do it:
create a class WcfMethodEntry which derives from PostSharp's aspect OnMethodBoundaryAspect,
annotate all WCF methods with WcfMethodEntry attribute,
in the WcfMethodEntry I serialize the method arguments to json with the JsonConvert.SerializeObject method and save it to the database.
This works ok, but sometimes the arguments are quite large, for example a custom class with a couple of byte arrays with photo, fingerprint etc. I would like to exclude all those byte array data types from serialization, what would be the best way to do it?
Example of a serialized json:
[
{
"SaveCommand":{
"Id":5,
"PersonalData":{
"GenderId":2,
"NationalityCode":"DEU",
"FirstName":"John",
"LastName":"Doe",
},
"BiometricAttachments":[
{
"BiometricAttachmentTypeId":1,
"Parameters":null,
"Content":"large Base64 encoded string"
}
]
}
}
]
Desired output:
[
{
"SaveCommand":{
"Id":5,
"PersonalData":{
"GenderId":2,
"NationalityCode":"DEU",
"FirstName":"John",
"LastName":"Doe",
},
"BiometricAttachments":[
{
"BiometricAttachmentTypeId":1,
"Parameters":null,
"Content":"..."
}
]
}
}
]
Edit: I can't change the classes that are used as arguments for web service methods - that also means that I cannot use JsonIgnore attribute.
The following allows you to exclude a specific data-type that you want excluded from the resulting json. It's quite simple to use and implement and was adapted from the link at the bottom.
You can use this as you cant alter the actual classes:
public class DynamicContractResolver : DefaultContractResolver
{
private Type _typeToIgnore;
public DynamicContractResolver(Type typeToIgnore)
{
_typeToIgnore = typeToIgnore;
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
properties = properties.Where(p => p.PropertyType != _typeToIgnore).ToList();
return properties;
}
}
Usage and sample:
public class MyClass
{
public string Name { get; set; }
public byte[] MyBytes1 { get; set; }
public byte[] MyBytes2 { get; set; }
}
MyClass m = new MyClass
{
Name = "Test",
MyBytes1 = System.Text.Encoding.Default.GetBytes("Test1"),
MyBytes2 = System.Text.Encoding.Default.GetBytes("Test2")
};
JsonConvert.SerializeObject(m, Formatting.Indented, new JsonSerializerSettings { ContractResolver = new DynamicContractResolver(typeof(byte[])) });
Output:
{
"Name": "Test"
}
More information can be found here:
Reducing Serialized JSON Size
You could just use [JsonIgnore] for this specific property.
[JsonIgnore]
public Byte[] ByteArray { get; set; }
Otherwise you can also try this: Exclude property from serialization via custom attribute (json.net)
Another way would be to use a custom type converter and have it return null, so the property is there but it will simply be null.
For example i use this so i can serialize Exceptions:
/// <summary>
/// Exception have a TargetSite property which is a methodBase.
/// This is useless to serialize, and can cause huge strings and circular references - so this converter always returns null on that part.
/// </summary>
public class MethodBaseConverter : JsonConverter<MethodBase?>
{
public override void WriteJson(JsonWriter writer, MethodBase? value, JsonSerializer serializer)
{
// We always return null so we don't object cycle.
serializer.Serialize(writer, null);
}
public override MethodBase? ReadJson(JsonReader reader, Type objectType, MethodBase? existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
return null;
}
}
Try to use the JsonIgnore attribute.

Deserialize derived classes using Json.net without using JObject

I have a large json dataset that I need to deserialize. I am using Json.net's JsonTextReader to read the data.
My problem is that I need to deserialize some derived classes, so I need to be able to "look ahead" for a particular property defining my data type. In the example below, the "type" parameter is used to determine the object type to deserialize.
{
type: "groupData",
groupParam: "groupValue1",
nestedObject:
{
type: "groupData",
groupParam: "groupValue2",
nestedObject:
{
type: "bigData",
arrayData: [ ... ]
}
}
My derived objects can be heavily nested and very deep. Loading the entire dataset in memory is not desired since it will require much memory. Once I get down to the "bigData" object, I will be processing the data (such as the array in the example above), but it will not be stored in memory (it is too big).
All solutions to my problem that I have seen so far have utilized JObject to deserialize the partial objects. I want to avoid using JObject because it will deserialize every object down the hierarchy repeatedly.
How can I solve my deserialization issue?
Is there any way to search ahead for the "type" parameter, then backtrack to the start of the object's { character to start processing?
I not aware of anyway to prempt the loading in of the object in order to specify a lookahead (at least not in Json.NET) but you could use the other attribute based configuration items at your disposal in order to ignore unwanted properties:
public class GroupData {
[JsonIgnore]
public string groupParam { get; set; }
[JsonIgnore]
public GroupData nestedObject { get; set; }
public string[] arrayData { get; set; }
}
Alternatively, you can give custom creation converters a try:
For example..
public class GroupData {
[JsonIgnore]
public string groupParam { get; set; }
[JsonIgnore]
public GroupData nestedObject { get; set; }
}
public class BigData : GroupData {
public string[] arrayData { get; set; }
}
public class ObjectConverter<T> : CustomCreationConverter<T>
{
public ObjectConverter() { }
public override bool CanConvert(Type objectType)
{
return objectType.Name == "BigData";
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Some additional checks/work?
serializer.Populate(reader, target);
}
}

Save Dictionary with nested array in MongoDB

The following class shall be received by an API as Json and stored in MongoDB, using the C# Driver and Web API. The data property is unstructured, but I can restrict it to key-value pairs with possibly nested arrays in these values.
public class Something
{
[BsonId, JsonIgnore]
public ObjectId _id { get; set; }
public IDictionary<string, object> data { get; set; }
}
When the json is posted from the client, Json.NET deserializes properly.
Saving the class to MongoDB, I get something like this in the database with the c# specific type:
{
property1: 'one',
property2: {
_t: 'System.Collections.Generic.List`1[System.Object]'
_v:
[
{}, {}, {}, ...
]
}
}
Based on these sources, I have pulled together a CustomCreationConverter for Json.NET that nests a List into the value of the Dictionary:
Apply JsonDictionaryAttributes to properties
Json.NET: Deserializing nested dictionaries
CustomCreationConverter Source Code
public class Something
{
...
[JsonProperty(ItemConverterType = typeof(CustomConverter))]
public IDictionary<string, object> data { get; set; }
}
with this override:
public class CustomConverter : CustomCreationConverter<IList<object>>
{
public override IList<object> Create(Type objectType)
{
return new List<object>();
}
public override bool CanConvert(Type objectType)
{
return true; // Just to keep it simple
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
return base.ReadJson(reader, objectType, existingValue, serializer);
return serializer.Deserialize(reader);
}
}
This works actually fine, but I still get the construction with the c# specific types in MongoDB. How can I get this data into MongoDB without the type-value properties when nested?
So I have this working with Dictionary<> rather than IDictionary<> without an custom converters, but I'm also using my own types rather than just object. I'm not clear what you mean by "useful way", but you can annotate your members with BsonDictionaryOptionsAttribute and pass in DictionaryRepresentation.Document or DictionaryRepresentation.ArrayOfDocument to change the shape persisted to MongoDB if that's what you mean?
Otherwise, what did you mean by "useful way" with regards to the way it's structured now? You'll alwways get the "_t" discriminator as long as there's more than one type possible. I'm guessing that you're seeing that because you're using IDictionary<>.

Categories