Detect If Deserialized Object Is Missing a Field With the Jsonconvert Class in Json.Net

Detect if deserialized object is missing a field with the JsonConvert class in Json.NET

The Json.Net serializer has a MissingMemberHandling setting which you can set to Error. (The default is Ignore.) This will cause the serializer to throw a JsonSerializationException during deserialization whenever it encounters a JSON property for which there is no corresponding property in the target class.

static void Main(string[] args)
{
try
{
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.MissingMemberHandling = MissingMemberHandling.Error;

var goodObj = JsonConvert.DeserializeObject<MyJsonObjView>(correctData, settings);
System.Console.Out.WriteLine(goodObj.MyJsonInt.ToString());

var badObj = JsonConvert.DeserializeObject<MyJsonObjView>(wrongData, settings);
System.Console.Out.WriteLine(badObj.MyJsonInt.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.GetType().Name + ": " + ex.Message);
}
}

Result:

42
JsonSerializationException: Could not find member 'SomeOtherProperty' on object
of type 'MyJsonObjView'. Path 'SomeOtherProperty', line 3, position 33.

See: MissingMemberHandling setting.

Using JsonConvert.DeserializeObject, how to warn about missing members, but continue deserializing?

You can do something like this:

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;

namespace NewtonsoftJsonSample
{
public static class Program
{
public static void Main(string[] args)
{
var json = "{'name': 'john', 'age': 45, 'city': 'Bristol'}".Replace("'", "\"");

// remember to set an error handler and to raise an error each time a member is missing
// during deserialization
var settings = new JsonSerializerSettings
{
Error = OnError,
MissingMemberHandling = MissingMemberHandling.Error
};

var deserialized = JsonConvert.DeserializeObject<Character>(json, settings);

Console.WriteLine("Deserialized object: {0}", deserialized);
Console.ReadLine();

static void OnError(object sender, ErrorEventArgs args)
{
Console.WriteLine("Unable to find member '{0}' on object of type {1}", args.ErrorContext.Member, args.ErrorContext.OriginalObject.GetType().Name);

// set the current error as handled
args.ErrorContext.Handled = true;
}
}
}

public class Character
{
public string Name { get; set; }

public override string ToString()
{
return $"Name: {this.Name}";
}
}
}

Basically you need to use both MissingMemberHandling and Error properties of JsonSerializerSettings class.

Json.NET : Detect an absence of a property on Json which appears to be a member of my object

Json.Net provides a way to achieve that.

You can set an attribute on the property in your Model class. and if
that property is not available in JSON it'll throw an exception.

Here is the Example

Model

public class Videogame
{
[JsonProperty(Required = Required.Always)]
public string Name { get; set; }

[JsonProperty(Required = Required.AllowNull)]
public DateTime? ReleaseDate { get; set; }
}

Test Code

string json = @"{
'Name': 'Starcraft III'
}";

Videogame starcraft = JsonConvert.DeserializeObject<Videogame>(json);

You can read more about this here

How to check for missing field in JSON using JSON.NET

The simplest option would be to make it an int? property, then check whether the value is null afterwards:

public class SomeClass
{
public string Foo { get; set; }
public int? Bar { get; set; }
}

...

var deserialized = JsonConvert.DeserializeObject<SomeClass>(json);
if (deserialized.Bar == null)
{
// Whatever you want to do if it wasn't set
}

Of course, the JSON could explicitly set the value to null, but I expect you'd probably be happy handling that as if it was missing.

How to get all not exist keys after JsonConvert DeserializeObject in Json.Net?

Your problem is twofold:

  1. Find the missing fields
  2. Find the extra fields

Before we are digging into the details let's split the CharaData into two classes

[Serializable]
public class CharaData
{
public int Hp;
public PlayerInfoData PlayerInfo;
}

[Serializable]
public class PlayerInfoData
{
public int Atk;
public int Def;
public int Spd;
}

Missing Fields

This solution relies on the JsonSchema

private static Lazy<JSchema> schema = new Lazy<JSchema>(() => {
var generator = new JSchemaGenerator();
return generator.Generate(typeof(CharaData));
}, true);

public static void ReportMissingFields(string json)
{
var semiParsed = JObject.Parse(json);

try
{
semiParsed.Validate(schema.Value);
}
catch (JSchemaValidationException ex)
{
Console.WriteLine(ex.ValidationError.Message);
}
}
  • schema stores the json schema of CharaData in a lazy fashion
  • Validate compares the json against the schema and if there is a mismatch then it throws a JSchemaValidationException
    • It exposes a property which type is ValidationError which contains a lots of information about the mismatch

Extra fields

This solution relies on JsonExtensionDataAttribute

[Serializable]
internal class CharaDataExtras: CharaData
{
[JsonExtensionData]
public IDictionary<string, JToken> ExtraFields;
}

...

public static void ReportExtraFields(string json)
{
var result = JsonConvert.DeserializeObject<CharaDataExtras>(json);
foreach (var field in result.ExtraFields)
{
Console.WriteLine($"An extra field has found, called {field.Key}");
}
}
  • I've defined the CharaData as class to be able to derive from it << CharaDataExtras
  • Every extra field will be put into the ExtraFields dictionary

Usage

var json = File.ReadAllText("sample.json");
ReportMissingFields(json);
ReportExtraFields(json);

The output:

Required properties are missing from object: Spd.
An extra field has found, called Mp

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

Another way to find null/undefined tokens during De-serialization is to write a custom JsonConverter , Here is an example of custom converter which can report both omitted tokens (e.g. "{ 'Id':5 }") and null tokens (e.g {"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 don't need to use it for serializing 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);
}
}

Throwing exception on deserializing type/naming-faulty JSON?

You could use Json.NET Schema at https://www.newtonsoft.com/jsonschema

This is from its home page:

JSchema schema = JSchema.Parse(@"{
'type': 'object',
'properties': {
'name': {'type':'string'},
'roles': {'type': 'array'}
}
}");

JObject user = JObject.Parse(@"{
'name': 'Arnie Admin',
'roles': ['Developer', 'Administrator']
}");

bool valid = user.IsValid(schema);
// true

How to set the value while deserialization if property not included in json string using custom JsonConverter

You can create a custom converter where you let Newtonsoft read and parse all the properties that are present in the json and you can manually assign values to the missing ones:

public class TestClassConverter : JsonConverter<TestClass>
{
private static readonly Random rand = new Random();
public override TestClass ReadJson(JsonReader reader, Type objectType, TestClass existingValue, bool hasExistingValue, JsonSerializer serializer)
{
//Let Newtonsoft do the heavy lifting
var jObject = JObject.Load(reader);
var target = new TestClass();
serializer.Populate(jObject.CreateReader(), target);

//Set the intact property manually
target.InternalID = rand.Next();
return target;
}

public override void WriteJson(JsonWriter writer, TestClass value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

I've used random, but you can assign whatever value you want.

Usage:

var data = JsonConvert.DeserializeObject<List<TestClass>>(json, converters: new TestClassConverter());

With this approach you don't have to decorate TestClass's InternalID property with a JsonConverterAttribute.


UPDATE: Non-generic JsonConverter version

public class TestClassConverter : JsonConverter
{
private static readonly Random rand = new Random();
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TestClass);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var target = new TestClass();
serializer.Populate(jObject.CreateReader(), target);

target.InternalID = rand.Next();
return target;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}


Related Topics



Leave a reply



Submit