NullValueHandling.Ignore with JsonConverter::WriteJson - c#

I am trying to perform custom serialisation, all the happy path code works but the null value path is not behaving as I'd like.
I have set the serializer settings to NullValueHandling.Ignore and other parts of my object graph that are null (and don't use my custom serialization) have the null values removed. It looks like the Newtonsoft serializer writes to a string builder so we should be able to 'rewind' any written json tokens but I don't see how to not write anything.
Doing nothing and just returning causes the serializer to throw an exception as the json would be invalid.
Any clues?
public class SpecialConvertor : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null || (int)value == 0)
{
if (serializer.NullValueHandling == NullValueHandling.Ignore)
{
//how to make this work?
}
else
{
writer.WriteNull();
}
return;
}
// the rest of WriteJson
}
// the rest of SpecialConvertor
}

NullValueHandling is for object references. In your example, your value is an integer. To omit integer properties with default values, use the setting DefaultValueHandling = DefaultValueHandling.Ignore.
The null check in WriteJson() should not be necessary because Json.NET never calls the converter with a null value. Instead, it writes the name & null value itself -- or not, if NullValueHandling == NullValueHandling.Ignore. So checking for null and rewinding should never be required.
A null value for an object property might still get written when null value handling or default value handling are Ignore if one of your converters writes it explicitly in WriteJson. To prevent that, you can check the settings and skip nulls like so:
public class MyClassConverter : JsonConverter
{
const string Prefix = "My Value Is: ";
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var myClass = (MyClass)value;
writer.WriteStartObject();
if (myClass.StringValue != null
|| (serializer.NullValueHandling != NullValueHandling.Ignore
&& (serializer.DefaultValueHandling & DefaultValueHandling.Ignore) != DefaultValueHandling.Ignore))
{
writer.WritePropertyName("StringValue");
if (myClass.StringValue == null)
writer.WriteNull();
else
serializer.Serialize(writer, Prefix + myClass.StringValue);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var s = (string)JValue.Load(reader);
if (s.StartsWith(Prefix))
s = s.Substring(Prefix.Length);
return s;
}
public override bool CanConvert(Type objectType) { return objectType == typeof(MyClass); }
}
[JsonConverter(typeof(MyClassConverter))]
public class MyClass
{
public string StringValue { get; set; }
}

Related

OnDeserialized callback with JsonConverter

I´m trying to use the JsonConverter from this answer to Can I specify a path in an attribute to map a property in my class to a child property in my JSON? by Brian Rogers to map nested properties in JSON to a flat object.
The converter works well, but I need to fire the OnDeserialized callback to fill other properties and it´s not fired. If I don´t use the converter, the callback is fired.
Examples:
string json = #"{
'response': {
'code': '000',
'description': 'Response success',
},
'employee': {
'name': 'Test',
'surname': 'Testing',
'work': 'At office'
}
}";
// employee.cs
public class EmployeeStackoverflow
{
[JsonProperty("response.code")]
public string CodeResponse { get; set; }
[JsonProperty("employee.name")]
public string Name { get; set; }
[JsonProperty("employee.surname")]
public string Surname { get; set; }
[JsonProperty("employee.work")]
public string Workplace { get; set; }
[OnDeserialized]
internal void OnDeserializedMethod(StreamingContext context)
{
Workplace = "At Home!!";
}
}
// employeeConverter.cs
public class EmployeeConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
object targetObj = Activator.CreateInstance(objectType);
foreach (PropertyInfo prop in objectType.GetProperties()
.Where(p => p.CanRead && p.CanWrite))
{
JsonPropertyAttribute att = prop.GetCustomAttributes(true)
.OfType<JsonPropertyAttribute>()
.FirstOrDefault();
string jsonPath = (att != null ? att.PropertyName : prop.Name);
JToken token = jo.SelectToken(jsonPath);
if (token != null && token.Type != JTokenType.Null)
{
object value = token.ToObject(prop.PropertyType, serializer);
prop.SetValue(targetObj, value, null);
}
}
return targetObj;
}
public override bool CanConvert(Type objectType)
{
// CanConvert is not called when [JsonConverter] attribute is used
return false;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
If I add [JsonConverter(typeof(EmployeeConverter))] in the Employee class I obtain:
=== With Converter ===
Code: 000
Name: Test
Surname: Testing
Workplace: At office
If I remove[JsonConverter(typeof(EmployeeConverter))] from the Employee class I obtain:
=== With Converter ===
Code:
Name:
Surname:
Workplace: At Home!!
My goal is to obtain:
=== With Converter ===
Code: 000
Name: Test
Surname: Testing
Workplace: At Home!!
Is the converter missing something?
Once you have created a custom JsonConverter for a type, it is incumbent on the converter to handle everything that needs to be done during deserialization -- including
Calling serialization callbacks.
Skipping ignored properties.
Invoking JsonConverter.ReadJson() for converters attached via attributes to members of the type.
Setting default values, skipping null values, resolving references, etc etc.
The complete logic can be seen in JsonSerializerInternalReader.PopulateObject(), and in theory you might need to make your ReadJson() method duplicate this method. (But in practice you will likely only implement a small, necessary subset of the logic.)
One way to make this task easier is to use Json.NET's own JsonObjectContract type metadata, as returned by JsonSerializer.ContractResolver.ResolveContract(objectType). This information contains the list of serialization callbacks and JsonpropertyAttribute property data used by Json.NET during deserialization. A modified version of the converter that uses this information would be as follows:
// Modified from this answer https://stackoverflow.com/a/33094930
// To https://stackoverflow.com/questions/33088462/can-i-specify-a-path-in-an-attribute-to-map-a-property-in-my-class-to-a-child-pr/
// By https://stackoverflow.com/users/10263/brian-rogers
// By adding handling of deserialization callbacks and some JsonProperty attributes.
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract ?? throw new JsonException(string.Format("{0} is not a JSON object", objectType));
var jo = JToken.Load(reader);
if (jo.Type == JTokenType.Null)
return null;
else if (jo.Type != JTokenType.Object)
throw new JsonSerializationException(string.Format("Unexpected token {0}", jo.Type));
var targetObj = contract.DefaultCreator();
// Handle deserialization callbacks
foreach (var callback in contract.OnDeserializingCallbacks)
callback(targetObj, serializer.Context);
foreach (var property in contract.Properties)
{
// Check that property isn't ignored, and can be deserialized.
if (property.Ignored || !property.Writable)
continue;
if (property.ShouldDeserialize != null && !property.ShouldDeserialize(targetObj))
continue;
var jsonPath = property.PropertyName;
var token = jo.SelectToken(jsonPath);
// TODO: default values, skipping nulls, PreserveReferencesHandling, ReferenceLoopHandling, ...
if (token != null && token.Type != JTokenType.Null)
{
object value;
// Call the property's converter if present, otherwise deserialize directly.
if (property.Converter != null && property.Converter.CanRead)
{
using (var subReader = token.CreateReader())
{
if (subReader.TokenType == JsonToken.None)
subReader.Read();
value = property.Converter.ReadJson(subReader, property.PropertyType, property.ValueProvider.GetValue(targetObj), serializer);
}
}
// TODO: property.ItemConverter != null
else
{
value = token.ToObject(property.PropertyType, serializer);
}
property.ValueProvider.SetValue(targetObj, value);
}
}
// Handle deserialization callbacks
foreach (var callback in contract.OnDeserializedCallbacks)
callback(targetObj, serializer.Context);
return targetObj;
}
Demo fiddle here.

Retrieving a Property Value Inside a Custom Attribute

Right now I have an attribute called Checkbox. We're using it because of our front end posts "On" and "Off" instead of true/false when a checkbox value is submitted.
Our goal is to parse the on/off values and convert them to true/false before they get to the JSON converter, so they can be picked up as a boolean.
I've considered using this attribute to handle that.
[Checkbox]
[JsonConverter(typeof(InvariantConverter))]
public bool CheckboxInputValue { get; set; }
I need access to the value of the property inside of the checkbox attribute and then need the ability to modify the value.
Open to suggestions and thoughts here.
You cannot store per-instance data in an attribute property since the attribute is created when using the reflection API on the class.
You should rather use a custom JSON converter to convert between the string values and the boolean value.
As Martin Ullrich already suggested you should consider using a dedicated JSON converter.
I left null value handling for you.
public class OnOffStringToBoolConverter : JsonConverter
{
private readonly Type _sourceType = typeof(string);
private readonly Type _targetType = typeof(bool);
public OnOffStringToBoolConverter()
{
}
public override bool CanRead => true;
public override bool CanWrite => true;
public override bool CanConvert(Type objectType)
{
if (objectType == null)
{
throw new ArgumentNullException(nameof(objectType));
}
return objectType == _sourceType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (objectType == null)
{
throw new ArgumentNullException(nameof(objectType));
}
if (serializer == null)
{
throw new ArgumentNullException(nameof(serializer));
}
if (reader.Value == null)
{
// Add some null handling logic here if needed.
throw new JsonSerializationException(
$"Unable to deserialize null value to {_targetType.Name}.");
}
if (string.Compare(reader.Value.ToString(), "On", StringComparison.OrdinalIgnoreCase) == 0)
{
return true;
}
if (string.Compare(reader.Value.ToString(), "Off", StringComparison.OrdinalIgnoreCase) == 0)
{
return false;
}
throw new JsonSerializationException(
$"Unable to deserialize '{reader.Value}' to {_targetType.FullName}. " +
$"This converter supports only \"On\", \"Off\" values.");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
if (serializer == null)
{
throw new ArgumentNullException(nameof(serializer));
}
if (value == null)
{
// Add some null handling logic here if needed.
throw new JsonSerializationException("Unable to serialize null value.");
}
// Write value only if it is boolean type.
if (value is bool boolValue)
{
writer.WriteValue(boolValue ? "On" : "Off");
}
else
{
throw new JsonSerializationException(
$"Unable to serialize '{value}' of type {value.GetType().FullName}. " +
$"This converter supports deserialization of values " +
$"of {_targetType.FullName} type only.");
}
}
}

How to force JsonConverter.WriteJson() to be called for a null value

I want to wrap some properties in a JSON object with some metadata, regardless if it's null or not. However, my custom JsonConverter.WriteJson override is not called in case the property is null.
What I get when property is not null:
{"Prop":{"Version":1, "Object":{"Content":"abc"}}}
What I get when it's null:
{"Prop":null}
What I want when it's null:
{"Prop":{"Version":1, "Object":null}}
Due to WriteJson never being called for null values, I do not get the opportunity to control this behavior. Is there any way to force this?
Note that I want to know if this is possible to do with e.g converters or contractresolvers, I can't/don't want to change the MyContent or Wrap classes (see below).
class VersioningJsonConverter : JsonConverter
{
//Does not get called if value is null !!
public override void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("v");
writer.WriteValue(1);
writer.WritePropertyName("o");
if(value == null)
{
//never happens
writer.WriteNull();
}
else
{
writer.WriteStartObject();
writer.WritePropertyName("Content");
writer.WriteValue((value as MyContent).Content);
writer.WriteEndObject();
}
writer.WriteEndObject();
}
public override Object ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
=> throw new NotImplementedException();
public override Boolean CanConvert(Type objectType) => objectType == typeof(MyContent);
public override Boolean CanRead => false;
}
public class MyContent
{
public String Content {get;set;}
}
public class Wrap
{
public MyContent Prop {get;set;}
}
There is no way currently to make Json.NET call JsonConverter.WriteJson() for a null value. This can be seen in JsonSerializerInternalWriter.SerializeValue(...) which immediately writes a null and returns for a null incoming value:
private void SerializeValue(JsonWriter writer, object value, JsonContract valueContract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerProperty)
{
if (value == null)
{
writer.WriteNull();
return;
}
// Remainder omitted
So if you need to translate null member(s) to non-null JSON value(s) but cannot modify the types themselves, you have two options:
Create a custom JsonConverter for the parent declaring type(s) of the member(s) that serializes every parent manually, OR
Create a custom contract resolver that translates the member(s) to ones returning some non-null surrogate or wrapper object.
Option #2 is more maintainable. The following contract resolver should do the job, wrapping the returned value of every member returning a value of the type(s) specified in the incoming list of types with the required version information:
public class CustomContractResolver : DefaultContractResolver
{
// Because contracts are cached, WrappedTypes must not be modified after construction.
readonly HashSet<Type> WrappedTypes = new HashSet<Type>();
public CustomContractResolver(IEnumerable<Type> wrappedTypes)
{
if (wrappedTypes == null)
throw new ArgumentNullException();
foreach (var type in wrappedTypes)
WrappedTypes.Add(type);
}
class VersionWrapperProvider<T> : IValueProvider
{
readonly IValueProvider baseProvider;
public VersionWrapperProvider(IValueProvider baseProvider)
{
if (baseProvider == null)
throw new ArgumentNullException();
this.baseProvider = baseProvider;
}
public object GetValue(object target)
{
return new VersionWrapper<T>(target, baseProvider);
}
public void SetValue(object target, object value) { }
}
class ReadOnlyVersionWrapperProvider<T> : IValueProvider
{
readonly IValueProvider baseProvider;
public ReadOnlyVersionWrapperProvider(IValueProvider baseProvider)
{
if (baseProvider == null)
throw new ArgumentNullException();
this.baseProvider = baseProvider;
}
public object GetValue(object target)
{
return new ReadOnlyVersionWrapper<T>(target, baseProvider);
}
public void SetValue(object target, object value) { }
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (WrappedTypes.Contains(property.PropertyType)
&& !(member.DeclaringType.IsGenericType
&& (member.DeclaringType.GetGenericTypeDefinition() == typeof(VersionWrapper<>) || member.DeclaringType.GetGenericTypeDefinition() == typeof(ReadOnlyVersionWrapper<>))))
{
var wrapperGenericType = (property.Writable ? typeof(VersionWrapper<>) : typeof(ReadOnlyVersionWrapper<>));
var providerGenericType = (property.Writable ? typeof(VersionWrapperProvider<>) : typeof(ReadOnlyVersionWrapperProvider<>));
var wrapperType = wrapperGenericType.MakeGenericType(new[] { property.PropertyType });
var providerType = providerGenericType.MakeGenericType(new[] { property.PropertyType });
property.PropertyType = wrapperType;
property.ValueProvider = (IValueProvider)Activator.CreateInstance(providerType, property.ValueProvider);
property.ObjectCreationHandling = ObjectCreationHandling.Reuse;
}
return property;
}
}
internal class VersionWrapper<T>
{
readonly object target;
readonly IValueProvider baseProvider;
public VersionWrapper(object target, IValueProvider baseProvider)
{
this.target = target;
this.baseProvider = baseProvider;
}
public int Version { get { return 1; } }
[JsonProperty(NullValueHandling = NullValueHandling.Include)]
public T Object
{
get
{
return (T)baseProvider.GetValue(target);
}
set
{
baseProvider.SetValue(target, value);
}
}
}
internal class ReadOnlyVersionWrapper<T>
{
readonly object target;
readonly IValueProvider baseProvider;
public ReadOnlyVersionWrapper(object target, IValueProvider baseProvider)
{
this.target = target;
this.baseProvider = baseProvider;
}
public int Version { get { return 1; } }
[JsonProperty(NullValueHandling = NullValueHandling.Include)]
public T Object
{
get
{
return (T)baseProvider.GetValue(target);
}
}
}
Then use it as follows to wrap all properties of type MyContent:
static IContractResolver resolver = new CustomContractResolver(new[] { typeof(MyContent) });
// And later
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var json = JsonConvert.SerializeObject(wrap, Formatting.Indented, settings);
Notes:
You should statically cache the contract resolver for performance reasons explained here.
VersionWrapperProvider<T> creates a wrapper object with the necessary version information as well as a surrogate Object property that gets and sets the underlying value using Json.NET's own IValueProvider.
Because Json.NET does not set back the value of a pre-allocated reference property, but instead simply populates it with the deserialized property values, it is necessary for the setter of VersionWrapper<T>.Object to itself set the value in the parent.
If your wrapped types are polymorphic, in CreateProperty() you may need to check whether any of the base types of property.PropertyType are in WrappedTypes.
Populating a pre-existing Wrap using JsonConvert.PopulateObject should be tested.
This solution may not work when deserializing properties passed to parameterized constructors. DefaultContractResolver.CreatePropertyFromConstructorParameter would need modification in such a situation.
Working sample .Net fiddle here.

Deserialize two values into the same property

I have a client which can call two different versions of a service.
One service only sends a single value:
{
"value" : { ... }
}
The second service always returns multiple values:
{
"values" : [
{ ... },
{ ... }
]
}
Ideally, I'd like to represent this with a single object in my client classes so the user never sees whether it's a single value or multiple values.
public class MyValues
{
public List<Stuff> Values { get; set; }
public Thing Other { get; set; }
}
I think that the only way I'll be able to accomplish this is with a custom JsonConverter class which I apply to MyValues, but I really only want to do something custom when I'm deserializing the property value. I can't seem to figure out if an IContractResolver would be a better way to go (e.g. somehow attach a phantom property to MyValues that deserializes value and puts it into Values.
If I create a custom converter, how to I tell it to deserialize everything else normally (e.g. if Other has an extra properties make sure they are handled appropriately, etc.)
Rather than writing a JsonConverter, you could make a set-only property Value on your MyValues, like so:
public class MyValues
{
[JsonProperty]
Stuff Value
{
set
{
(Values = Values ?? new List<Stuff>(1)).Clear();
Values.Add(value);
}
}
public List<Stuff> Values { get; set; }
public Thing Other { get; set; }
}
It could be public or private if marked with [JsonProperty]. In this case Json.NET will call the Value setter if the singleton "value" property is encountered in the JSON, and call the Values setter if the array "values" property is encountered. Since the property is set-only only the array property will be re-serialized.
To make a custom JsonConverter that has special processing for a few properties of a type but uses default processing for the remainder, you can load the JSON into a JObject, detach and process the custom properties, then populate the remainder from the JObject with JsonSerializer.Populate(), like so:
class MyValuesConverter : CustomPropertyConverterBase<MyValues>
{
protected override void ProcessCustomProperties(JObject obj, MyValues value, JsonSerializer serializer)
{
// Remove the value property for manual deserialization, and deserialize
var jValue = obj.GetValue("value", StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent();
if (jValue != null)
{
(value.Values = value.Values ?? new List<Stuff>()).Clear();
value.Values.Add(jValue.ToObject<Stuff>(serializer));
}
}
}
public abstract class CustomPropertyConverterBase<T> : JsonConverter where T : class
{
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var jObj = JObject.Load(reader);
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
var value = existingValue as T ?? (T)contract.DefaultCreator();
ProcessCustomProperties(jObj, value, serializer);
// Populate the remaining properties.
using (var subReader = jObj.CreateReader())
{
serializer.Populate(subReader, value);
}
return value;
}
protected abstract void ProcessCustomProperties(JObject obj, T value, JsonSerializer serializer);
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
}

Convert empty strings to null with Json.Net

I'm having trouble finding a way to automatically deserialize (server side) all EmptyOrWhiteSpace strings to null . Json.Net by default simply assigns the value to the object property, and I need to verify string by string whether it is empty or white space, and then set it to null.
I need this to be done upon deserialization, so I don't have to remember to verify every single string that comes from the client.
How can I override this on Json Net?
After a lot of source digging, I solved my problem.
Turns out all the solutions proposed in the comments only work if I am deserializing a complex object which contains a property that is a string.
In this case, yes, simply modifying the contract resolver works [1].
However, what I needed was a way to convert any string to null upon deserialization, and modifying the contract this way will fail for the case where my object is just a string, i.e.,
public void MyMethod(string jsonSomeInfo)
{
// At this point, jsonSomeInfo is "\"\"",
// an emmpty string.
var deserialized = new JsonSerializer().Deserialize(new StringReader(jsonSomeInfo), typeof(string));
// deserialized = "", event if I used the modified contract resolver [1].
}
What happens is that when we work with a complex object, internally JSON.NET assigns a TokenType of JsonToken.StartObject to the reader, which will cause the deserialization to follow a certain path where property.ValueProvider.SetValue(target, value); is called.
However, if the object is just a string, the TokenType will be JsonToken.String, and the path will be different, and the value provider will never be invoked.
In any event, my solution was to build a custom converter to convert JsonReaders that have TokenType == JsonToken.String (code below).
Solution
public class StringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value == null) return null;
string text = reader.Value.ToString();
if (string.IsNullOrWhiteSpace(text))
{
return null;
}
return text;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException("Not needed because this converter cannot write json");
}
public override bool CanWrite
{
get { return false; }
}
}
[1] Credits to #Raphaël Althaus.
public class NullToEmptyStringResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return type.GetProperties()
.Select(p => {
var jp = base.CreateProperty(p, memberSerialization);
jp.ValueProvider = new EmptyToNullStringValueProvider(p);
return jp;
}).ToList();
}
}
public class EmptyToNullStringValueProvider : IValueProvider
{
PropertyInfo _MemberInfo;
public EmptyToNullStringValueProvider(PropertyInfo memberInfo)
{
_MemberInfo = memberInfo;
}
public object GetValue(object target)
{
object result = _MemberInfo.GetValue(target);
if (_MemberInfo.PropertyType == typeof(string) && result != null && string.IsNullOrWhiteSpace(result.ToString()))
{
result = null;
}
return result;
}
public void SetValue(object target, object value)
{
if (_MemberInfo.PropertyType == typeof(string) && value != null && string.IsNullOrWhiteSpace(value.ToString()))
{
value = null;
}
_MemberInfo.SetValue(target, value);
}
}

Categories

Resources