How to configure JSON.net deserializer to track for missing properties?

Class Example:

public class ClassA { public int Id { get; set; } public string SomeString { get; set; } public int? SomeInt { get; set; } } 

Default Desiler:

 var myObject = JsonConvert.DeserializeObject<ClassA>(str); 

Create the same object for two different inputs

 {"Id":5} 

or

 {"Id":5,"SomeString":null,"SomeInt":null} 

How can I track properties that are missing during the deserialization process and maintain the same behavior? Is there a way to override some JSON.net serialization methods (like the methods of the DefaultContractResolver class) to achieve this. For instance:

 List<string> missingProps; var myObject = JsonConvert.DeserializeObject<ClassA>(str, settings, missingProps); 

The first input list must contain the names of the missing properties ("SomeString", "SomeInt"), and for the second input it must be empty. The deserialized object remains the same.

+5
source share
3 answers

Another way to search for null / undefined tokens during de-serialization is to write a custom JsonConverter . The following is an example of a custom converter that can report missing tokens (for example, "{ 'Id':5 }" ) and zero tokens (for example, {"Id":5,"SomeString":null,"SomeInt":null} )

 public class NullReportConverter : JsonConverter { private readonly List<PropertyInfo> _nullproperties=new List<PropertyInfo>(); public bool ReportDefinedNullTokens { get; set; } public IEnumerable<PropertyInfo> NullProperties { get { return _nullproperties; } } public void Clear() { _nullproperties.Clear(); } public override bool CanConvert(Type objectType) { return true; } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { existingValue = existingValue ?? Activator.CreateInstance(objectType, true); var jObject = JObject.Load(reader); var properties = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (var property in properties) { var jToken = jObject[property.Name]; if (jToken == null) { _nullproperties.Add(property); continue; } var value = jToken.ToObject(property.PropertyType); if(ReportDefinedNullTokens && value ==null) _nullproperties.Add(property); property.SetValue(existingValue, value, null); } return existingValue; } //NOTE: we can omit writer part if we only want to use the converter for deserializing public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var objectType = value.GetType(); var properties = objectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); writer.WriteStartObject(); foreach (var property in properties) { var propertyValue = property.GetValue(value, null); writer.WritePropertyName(property.Name); serializer.Serialize(writer, propertyValue); } writer.WriteEndObject(); } } 

Note: we can omit the Writer part if we do not need to use it to serialize objects.

Usage example:

 class Foo { public int Id { get; set; } public string SomeString { get; set; } public int? SomeInt { get; set; } } class Program { static void Main(string[] args) { var nullConverter=new NullReportConverter(); Console.WriteLine("Pass 1"); var obj0 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5, \"Id\":5}", nullConverter); foreach(var p in nullConverter.NullProperties) Console.WriteLine(p); nullConverter.Clear(); Console.WriteLine("Pass2"); var obj1 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5,\"SomeString\":null,\"SomeInt\":null}" , nullConverter); foreach (var p in nullConverter.NullProperties) Console.WriteLine(p); nullConverter.Clear(); nullConverter.ReportDefinedNullTokens = true; Console.WriteLine("Pass3"); var obj2 = JsonConvert.DeserializeObject<Foo>("{\"Id\":5,\"SomeString\":null,\"SomeInt\":null}", nullConverter); foreach (var p in nullConverter.NullProperties) Console.WriteLine(p); } } 
+3
source

1. JSON has a property that is not in your class

Using the JsonSerializerSettings.MissingMemberHandling property, you can tell if missing properties are treated as errors.

Than you can install the Error delegate, which will log errors.

This will detect if there is any โ€œgarbageโ€ property in the JSON string.

 public class ClassA { public int Id { get; set; } public string SomeString { get; set; } } internal class Program { private static void Main(string[] args) { const string str = "{'Id':5, 'FooBar': 42 }"; var myObject = JsonConvert.DeserializeObject<ClassA>(str , new JsonSerializerSettings { Error = OnError, MissingMemberHandling = MissingMemberHandling.Error }); Console.ReadKey(); } private static void OnError(object sender, ErrorEventArgs args) { Console.WriteLine(args.ErrorContext.Error.Message); args.ErrorContext.Handled = true; } } 

2. Your class has a property that is not in JSON

Option 1:

Make it mandatory:

  public class ClassB { public int Id { get; set; } [JsonProperty(Required = Required.Always)] public string SomeString { get; set; } } 
Option 2:

Use some โ€œspecialโ€ value as the default value and then check it.

 public class ClassB { public int Id { get; set; } [DefaultValue("NOTSET")] public string SomeString { get; set; } public int? SomeInt { get; set; } } internal class Program { private static void Main(string[] args) { const string str = "{ 'Id':5 }"; var myObject = JsonConvert.DeserializeObject<ClassB>(str , new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); if (myObject.SomeString == "NOTSET") { Console.WriteLine("no value provided for property SomeString"); } Console.ReadKey(); } } 
Option 3:

Another good idea would be to encapsulate this check as an istself class. Create the Verify() method as shown below and call it after deserialization.

 public class ClassC { public int Id { get; set; } [DefaultValue("NOTSET")] public string SomeString { get; set; } public int? SomeInt { get; set; } public void Verify() { if (SomeInt == null ) throw new JsonSerializationException("SomeInt not set!"); if (SomeString == "NOTSET") throw new JsonSerializationException("SomeString not set!"); } } 
+7
source

I had this problem, but defaultValue was not a solution due to the POCO object. I think this is a simpler approach than NullReportConverter. There are three unit tests. Root is a class that encapsulates all json. The key is the type of property. Hope this helps someone.

 using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; namespace SomeNamespace { [TestClass] public class NullParseJsonTest { [TestMethod] public void TestMethod1() { string slice = "{Key:{guid:\"asdf\"}}"; var result = JsonConvert.DeserializeObject<Root>(slice); Assert.IsTrue(result.OptionalKey.IsSet); Assert.IsNotNull(result.OptionalKey.Value); Assert.AreEqual("asdf", result.OptionalKey.Value.Guid); } [TestMethod] public void TestMethod2() { string slice = "{Key:null}"; var result = JsonConvert.DeserializeObject<Root>(slice); Assert.IsTrue(result.OptionalKey.IsSet); Assert.IsNull(result.OptionalKey.Value); } [TestMethod] public void TestMethod3() { string slice = "{}"; var result = JsonConvert.DeserializeObject<Root>(slice); Assert.IsFalse(result.OptionalKey.IsSet); Assert.IsNull(result.OptionalKey.Value); } } class Root { public Key Key { get { return OptionalKey.Value; } set { OptionalKey.Value = value; OptionalKey.IsSet = true; // This does the trick, it is never called by JSON.NET if attribute missing } } [JsonIgnore] public Optional<Key> OptionalKey = new Optional<Key> { IsSet = false }; }; class Key { public string Guid { get; set; } } class Optional<T> { public T Value { get; set; } public bool IsSet { get; set; } } } 
+1
source

All Articles