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.
Related
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.
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);
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.
I have some JSON like:
{
"companyName": "Software Inc.",
"employees": [
{
"employeeName": "Sally"
},
{
"employeeName": "Jimmy"
}
]
}
I want to deserialize it into:
public class Company
{
public string companyName { get; set; }
public IList<Employee> employees { get; set; }
}
public class Employee
{
public string employeeName { get; set; }
public Company employer { get; set; }
}
How can I have JSON.NET set the "employer" reference? I tried using a CustomCreationConverter, but the public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) method doesn't contain a any reference to the current parent object.
That's only going to cause you headaches if you're trying to do that as part of the deserialization. It'd be much easier to perform that task after deserialization. Do something like:
var company = //deserialized value
foreach (var employee in company.employees)
{
employee.employer = company;
}
Or a one-liner, if you prefer the syntax:
company.employees.ForEach(e => e.employer = company);
I have handled a similar situation by defining a "callback" in the parent class like so:
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
// Add logic here to pass the `this` object to any child objects
}
This works with JSON.Net without any other setup. I have not actually needed the StreamingContext object.
In my case the child objects have a SetParent() method that gets called here and also when a new child object is created in other ways.
[OnDeserialized] is from System.Runtime.Serialization, and so you won't need to add a JSON library reference.
Json.net solved this with PreserveReferencesHandling. Simply set PreserveReferencesHandling = PreserveReferencesHandling.Objects and Newtonsoft does it all for you.
https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_PreserveReferencesHandling.htm
Regards,
Fabianus
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);
}
}