Json MissingMemberHandling when using converters - c#

Deserializing a saved interface object using Json.NET without defining it's implementation type was throwing errors (of course) so I wrote a re-usable class:
public class MakeJsonConverter<TParent, TChild> : JsonConverter where TChild:TParent
{
private readonly Type _parent;
private Type _child;
public MakeJsonConverter()
{
_parent = typeof(TParent);
_child = typeof(TChild);
}
public override bool CanConvert(Type objectType)
{
return (objectType == _parent);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return serializer.Deserialize<TChild>(reader);
}
//This should never be called. Only reading from saved interface data was an issue without using a converter.
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
It works fine, except for the fact that the settings I pass to it don't seem to be used. Actually I don't know that for sure, what I do know is that MissingMemberHandling.Error isn't working.
public virtual string SerializeWithConverter<TParent, TChild>(TParent obj) where TChild : TParent
{
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new DefaultContractResolver
{
DefaultMembersSearchFlags =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance
},
TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
//Settings important to this situation
MissingMemberHandling = MissingMemberHandling.Error,
Converters = new List<JsonConverter> { new MakeJsonConverter<TParent, TChild>() }
};
return JsonConvert.SerializeObject(obj, Formatting.Indented, settings);
}
I can't seem to figure out why this is working the way it is.. can anyone explain?

Try adding TypeNameHandling = TypeNameHandling.All when serializing and deserializing
This might help the deserializer in case you have nested objects structures which you want to deserialize

What exactly do you mean when you say MissingMemberHandling.Error isn't working?
I'm assuming it's because you are expecting an error when a user over/under posts an object that doesn't match your object? Either way, I believe the MissingMemberHandling.Error won't throw when you set your own custom ContractResolver. Instead, create your own class that inherits from DefaultContractResolver and override the JsonObjectContract. This will provide a result similar to what you are expecting.
public class CustomContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.ItemRequired = Required.Always;
return contract;
}
}
Then use that as your contract resolver
ContractResolver = new CustomContractResolver(),
Tested and worked for me

Related

Json.Net Self Recurse on Custom Converter

The details of the problem might be a little long, so I'll describe it in short at the beginning: How to force Json.Net to use its default object serializer(or ignore a specific custom converter in other words), but still keep the settings in a JsonSerializer, when deserializing an object?
Apologize for my poor English, the description may be kind of ambiguous and confusing. I'll explain it with my detailed scenario.
When dealing with HTTP responses, we sometimes encounter a scenario that an object is the only child of its parent, making the parent object a meaningless object wrapper to some extent. In some poor designs, there could be multiple levels of such wrappers. If we want such JSON deserialized properly without customizing, we have to follow the structure to define those wrapper classes, which is definitely pointless and annoying, thus I came up with the idea to create a general-purpose ObjectWrapperConverter. Here's the code:
public class ObjectWrapperConverter<T> : ObjectWrapperConverterBase<T> {
public ObjectWrapperConverter(string propertyName) : this(propertyName, Array.Empty<JsonConverter>()) { }
public ObjectWrapperConverter(string propertyName, params JsonConverter[] converters) {
PropertyName = propertyName;
Converters = converters;
}
public override string PropertyName { get; }
public override JsonConverter[] Converters { get; }
}
public abstract class ObjectWrapperConverterBase<T> : JsonConverter<T> {
public abstract string PropertyName { get; }
public abstract JsonConverter[] Converters { get; }
public sealed override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) {
writer.WriteStartObject();
writer.WritePropertyName(PropertyName);
serializer.Converters.AddRange(Converters);
writer.WriteValue(value, serializer);
serializer.Converters.RemoveRange(Converters);
writer.WriteEndObject();
}
public sealed override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) {
var token = JToken.Load(reader);
if (token.Type != JTokenType.Object)
throw new JTokenTypeException(token, JTokenType.Object);
var obj = token as JObject;
var prop = obj!.Property(PropertyName);
if (prop is null)
throw new JTokenException(token, $"Property \"{PropertyName}\" not found");
serializer.Converters.AddRange(Converters);
var result = prop.Value.ToObject<T>(serializer);//BUG: recurse when applying JsonConverterAttribute to a class
serializer.Converters.RemoveRange(Converters);
return result;
}
}
It works fine when I put JsonConverterAttribute on properties and fields. But when annotating class, problem occurs: the deserialization process fall into a recursive loop.
I debugged into Json.Net framework, and realized that when specifying a custom converter for a class, Json.Net will always use this converter to handle the serialization of this class unless higher-priority attribute (like JsonConverterAttribute placed on properties) is annotated. Thus, in my converter, the line where I put a comment will finally lead to a recurse.
If you've understood the purpose of this converter, it's easy to find out that this converter is just a middleware: add or remove the wrapper object, and continue the original serialization process.
So, how can I continue the "original" serialization process instead of falling into the converter itself again?
I made a deeper exploration into the Newtonsoft.Json framework and found out how it serialize and deserialize objects without converters.
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue
Because these classes and methods are internal, I have to use Reflection to invoke them. Thus I encapsulate this into two extension methods:
public static class NewtonsoftExtensions{
private static Type JsonSerializerInternalReader { get; } = typeof(JsonSerializer).Assembly.GetType("Newtonsoft.Json.Serialization.JsonSerializerInternalReader");
private static Type JsonSerializerInternalWriter { get; } = typeof(JsonSerializer).Assembly.GetType("Newtonsoft.Json.Serialization.JsonSerializerInternalWriter");
private static MethodInfo CreateValueInternal { get; } = JsonSerializerInternalReader.GetMethod("CreateValueInternal", BindingFlags.NonPublic | BindingFlags.Instance);
private static MethodInfo SerializeValue { get; } = JsonSerializerInternalWriter.GetMethod("SerializeValue", BindingFlags.NonPublic | BindingFlags.Instance);
public object DeserializeWithoutContractConverter(this JsonSerializer serializer, JsonReader reader, Type objectType) {
var contract = serializer.ContractResolver.ResolveContract(objectType);
var converter = contract.Converter;
contract.Converter = null;
object internalReader = Activator.CreateInstance(JsonSerializerInternalReader, serializer);
object result = CreateValueInternal.Invoke(internalReader, reader, objectType, contract, null, null, null, null);
contract.Converter = converter; //DefaultContractResolver caches the contract of each type, thus we need to restore the original converter for future use
return result;
}
public void SerializeWithoutContractConverter(this JsonSerializer serializer, JsonWriter writer, object value) {
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
var converter = contract.Converter;
contract.Converter = null;
object internalWriter = Activator.CreateInstance(JsonSerializerInternalWriter, serializer);
SerializeValue.Invoke(internalWriter, writer, value, contract, null, null, null);
contract.Converter = converter;
}
}
Using reflection to call internal methods is risky and should not be recommended, but compared with other answers in JSON.Net throws StackOverflowException when using [JsonConvert()], such approach would make full use of serializer settings. If the converter is general-purpose, like the ObjectWrapperConverter I'm trying to implement, this will cause least the unexpected results, as Newtonsoft.Json has tons of settings for users to customize the behaviors.

Unexpected token: StartObject, when deserializing OneOf union type using custom converter

I am trying to deserialize the following JSON (which validates on https://jsonlint.com/):
{"pandoc-api-version":[1,22],"meta":{"title":{"t":"MetaBlocks","c":[{"t":"Para","c":[{"t":"Str","c":"Dynamic"},{"t":"Space"},{"t":"Str","c":"Language"},{"t":"Space"},{"t":"Str","c":"Runtime"}]},{"t":"Para","c":[]}]}},"blocks":[{"t":"Para","c":[{"t":"Strong","c":[{"t":"Str","c":"Bill"},{"t":"Space"},{"t":"Str","c":"Chiles"},{"t":"Space"},{"t":"Str","c":"and"},{"t":"Space"},{"t":"Str","c":"Alex"},{"t":"Space"},{"t":"Str","c":"Turner"}]}]},{"t":"Para","c":[{"t":"Emph","c":[{"t":"Strong","c":[{"t":"Str","c":"Reading"},{"t":"Space"},{"t":"Str","c":"this"},{"t":"Space"},{"t":"Str","c":"Document:"}]}]}]}]}
into the following classes:
internal record TagContent(string T, OneOf<TagContent[], string>? C);
internal class RawPandoc {
[JsonProperty] public int[] PandocApiVersion = default!;
[JsonProperty] public Dictionary<string, TagContent> Meta = default!;
[JsonProperty] public TagContent[] Blocks = default!;
}
using the following code:
var settings = new JsonSerializerSettings {
ContractResolver = new DefaultContractResolver { NamingStrategy = new KebabCaseNamingStrategy() },
Converters = new JsonConverter[] { new OneOfJsonConverter() }
};
var pandoc = JsonConvert.DeserializeObject<RawPandoc>(s, settings);
and I get the following error:
Unexpected token when deserializing object: StartObject. Path 'meta.title.c[0]', line 1, position 69.
How can I resolve this?
For completeness, here is the current and incomplete code for OneOfJsonConverter. OneOf is a library for union types in C#:
using OneOf;
namespace PandocFilters {
public class OneOfJsonConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) {
if (value is IOneOf of) {
value = of.Value;
}
serializer.Serialize(writer, value);
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
if (reader.Value is null) { return null; }
// TODO not implemented yet
return reader.Value;
}
public override bool CanConvert(Type objectType) => objectType.UnderlyingIfNullable().GetInterfaces().Contains(typeof(IOneOf));
}
}
Problem is you are not advancing the reader in your ReadJson implementation. You declared your converter can handle IOneOf objects, and so JSON.NET expects your converter to actually read and handle it, however it does nothing as of now. So ReadJson is called (at the start of first array in json which should be deserialized to OneOf), and then after it returns - reader position is still where it was before (at start of array), which is not what JSON.NET expects. Then it fails trying to continue reading next object, because its assumptions are violated. So, just implement ReadJson, and meanwhile you can advance a reader for example like that:
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) {
// advance reader as expected
var eitherStringOrArray = JObject.ReadFrom(reader);
return reader.Value;
}

How can I customize the deserialization of a nested property in Json.net without adding attributes

Suppose I have these classes:
public class Bar
{
public Foo MyFoo { get; set; }
}
public class Foo
{
public string[] Stuff { get; set; }
}
And I have this JSON structure:
{
"MyFoo":
{
"Stuff":"those,are,my,stuff"
}
}
And I have a code path where a JObject is being converted to Bar using code like below:
myJObject.ToObject(typeof(Bar))
Now what I need to do is to supply the ToObject with a custom serializer to convert the string property Stuff into an array of string (using string.Split(...).ToArray())
I was asked not to add attributes on the client class 'Bar' so after looking around it seemed like a ContractResolver is in order but the issue is that the resolver only lets me handle direct properties of the root Type, that is Bar in my example, and I can't register a JsonConverter on a nested property.
So my question to you guys is, is this even achievable using Json.net?
Note that I need to do this not only for the Bar class but to an unlimited amount of classes with unknown structure so I can't hard-code a solution that will work for one type of class.
Based on your description, I don't see why you would need a ContractResolver. You know that the properties you want to deserialize specially will always be of type string[], so just make a converter that handles that type. Maybe something like this:
public class CsvStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string[]);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Null)
return null;
if (token.Type == JTokenType.String)
return ((string)token).Split(',');
if (token.Type == JTokenType.Array)
return token.ToObject<string[]>(serializer);
throw new JsonException("Unexpected token type: " + token.Type);
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, to use it with your JObject, create a new JsonSerializer instance, add the converter to it, and pass the serializer to the ToObject() method like this:
var serializer = new JsonSerializer();
serializer.Converters.Add(new CsvStringConverter());
var bar = myJObject.ToObject<Bar>(serializer);
Working demo here: https://dotnetfiddle.net/qmeBoh

How can I serialise PSObjects in C# with JSON.NET?

I am writing a Cmdlet and need to pass object structures into an API client that may contain PSObjects. Currently, these serialise as a JSON string containing CLIXML. Instead, I need it to be treated like an object (including the NoteProperties in PSObject.Properties as properties, and recursively serialising their values).
I tried writing my own JsonConverter but for some reason it only gets called for the top level object, not for nested PSObjects:
public class PSObjectJsonConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
if (value is PSObject) {
JObject obj = new JObject();
foreach (var prop in ((PSObject)value).Properties) {
obj.Add(new JProperty(prop.Name, value));
}
obj.WriteTo(writer);
} else {
JToken token = JToken.FromObject(value);
token.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
throw new NotImplementedException();
}
public override bool CanRead {
get { return false; }
}
public override bool CanConvert(Type objectType) {
return true;
}
}
Additionally, I am using serializing to camel case using CamelCasePropertyNamesContractResolver. Is there a way to make the converter respect that?
The following converter should correctly serialize recursively nested objects of type PSObject:
public class PSObjectJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(PSObject).IsAssignableFrom(objectType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var psObj = (PSObject)value;
writer.WriteStartObject();
foreach (var prop in psObj.Properties)
{
//Probably we shouldn't try to serialize a property that can't be read.
//https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.pspropertyinfo.isgettable?view=powershellsdk-1.1.0#System_Management_Automation_PSPropertyInfo_IsGettable
if (!prop.IsGettable)
continue;
writer.WritePropertyName(prop.Name);
serializer.Serialize(writer, prop.Value);
}
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead { get { return false; } }
}
Notes:
In WriteJson you serialize the incoming object value as the value of each property. Surely you meant prop.Value.
By only returning true from CanConvert() when the incoming object type is of type PSObject, you avoid the need to implement default serialization for non-PSObject types in WriteJson().
When you call JToken.FromObject(value) you are not using the incoming JsonSerializer serializer. Thus, any JsonSerializerSettings (including converters) will be lost. In theory you could use JToken.FromObject(Object, JsonSerializer) instead, which would preserve settings, but if you did, you would encounter the bug described in JSON.Net throws StackOverflowException when using [JsonConvert()]. Luckily, since we now return false from CanConvert when default serialization is required, this is no longer necessary.
There is no need to construct an intermediate JObject. You can write directly to the JsonWriter, which will be somewhat more performant.
Update: Additionally, I am using serializing to camel case using CamelCasePropertyNamesContractResolver. Is there a way to make the converter respect that?
Once you introduce a custom JsonConverter for your type, you need to do everything manually, including remapping of property names. Here's a version of WriteJson() that handles this by using DefaultContractResolver.NamingStrategy:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var psObj = (PSObject)value;
writer.WriteStartObject();
var resolver = serializer.ContractResolver as DefaultContractResolver;
var strategy = (resolver == null ? null : resolver.NamingStrategy) ?? new DefaultNamingStrategy();
foreach (var prop in psObj.Properties)
{
//Probably we shouldn't try to serialize a property that can't be read.
//https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.pspropertyinfo.isgettable?view=powershellsdk-1.1.0#System_Management_Automation_PSPropertyInfo_IsGettable
if (!prop.IsGettable)
continue;
writer.WritePropertyName(strategy.GetPropertyName(prop.Name, false));
serializer.Serialize(writer, prop.Value);
}
writer.WriteEndObject();
}
Note that naming strategies were introduced in Json.NET 9.0.1 so if you are using an earlier version you will need to create your own camel case name mapper such as the one shown in this answer.

JSON.NET custom constructor

as a partial problem of this question JSON.NET CustomCreationConverter with nested objects I tried to call a custom constructor during deserialization. My simplified class hierarchy is as follows:
public abstract class BusinessObjectBase
{
internal BusinessObjectBase(SerializationContext context)
: base(context)
{
}
}
public abstract class EditableObjectBase : BusinessObjectBase
{
protected EditableObjectBase(SerializationContext context)
: base(context)
{
}
}
public class EditableObjectCollection<TObject> : BusinessObjectBase, ICollection<TObject>, IList, INotifyCollectionChanged where TObject : BusinessObjectBase
{
protected EditableObjectCollection(SerializationContext context)
: base(context)
{
}
}
I know the object hierarchy to a certain level, but users are allowed / forced to derive their own classes. My idea was to write a custom creation converter. The problem I'm fighting with is that a property in a serialized object can be declared as BusinessObjectBase which is abstract, but the real object will be a more derived class and might be a collection or not. A CustomCreationConverter only gets this abstract type passed to the Create method and of course can't create the correct type from this information.
Inspired from this How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects I implemented a converter as follows:
internal class BusinessObjectCreationConverter : JsonConverter
{
public override bool CanWrite
{
get
{
return false;
}
}
public override bool CanConvert(Type objectType)
{
return typeof(BusinessObjectBase).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object result = null;
if (reader.TokenType != JsonToken.Null)
{
JObject jsonObject = JObject.Load(reader);
result = this.Create(objectType, jsonObject);
Verification.Assert<NullReferenceException>(result != null, "No Business Object created.");
serializer.Populate(jsonObject.CreateReader(), result);
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public BusinessObjectBase Create(Type objectType, JObject jsonObject)
{
JToken token = jsonObject.SelectToken("$type");
var typeString = token.Value<string>();
Type type = Type.GetType(typeString);
var businessObject = type.CreateUsingDesrializationConstructor<BusinessObjectBase>();
businessObject.Initialize(true);
return businessObject;
}
}
My class to test serialization looks like this:
public class AnyPocoContainingBusinessObject
{
public BusinessObjectBase BusinessObject { get; set; }
}
public class TestEditableObject : EditableObjectBase
{
internal TestEditableObject(SerializationContext context)
: base(context)
{
}
}
If I initialize my class my class with an collection
var collection = new EditableObjectCollection<TestEditableObject>(null);
var poco = new AnyPocoContainingBusinessObject { BusinessObject = collection };
and configure the serializer this way:
public NewtonsoftJsonSerializer()
: this(new JsonSerializer
{
TypeNameHandling = TypeNameHandling.Auto,
ObjectCreationHandling = ObjectCreationHandling.Replace,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
DefaultValueHandling = DefaultValueHandling.Ignore,
ContractResolver = new KsJsonContractResolver()
})
{
this.serializer.Converters.Add(new ReadOnlyObjectCollectionConverter());
this.serializer.Converters.Add(new BusinessObjectCreationConverter());
this.serializer.TraceWriter = new ConsoleTraceWriter();
}
I get an exception:
Cannot populate JSON object onto type 'KS.Interfaces.Core.Entities.EditableObjectCollection`1[KS.Interfaces.Core.Entities.Tests.Unit.EditableObjectCollectionTests+TestEditableObject]'. Path '$type', line 1, position 47.
in this code line of my converter:
serializer.Populate(jsonObject.CreateReader(), result);
Can any body tell me what might be the reason? I'm pretty sure that I created the correct type and with a EditableObjectBase derived object everything is fine. Only collections doesn't seem to work.
Any hints are highly appreciated, thank in advance
Carsten
even I haven't found a way to make my converter work there is one thing I learned during debugging the problem:
It seems that a converter should return an object that still has the same JsonToken value. In my case the JsonToken of the original object was JsonToken.Object, but for my conversion result the correct token value would be JsonToken.Array, but the reader still sees JsonToken.Object. At this point I stopped my research since I found a better way to call my custom constructor.
I wrote my own contract resolver:
internal class BusinessBaseContractResolver : DefaultContractResolver
{
public BusinessBaseContractResolver()
{
this.DefaultMembersSearchFlags |= BindingFlags.NonPublic;
}
public override JsonContract ResolveContract(Type type)
{
JsonContract contract = base.ResolveContract(type);
if (typeof(BusinessObjectBase).IsAssignableFrom(type))
{
contract.DefaultCreator = delegate
{
var businessObject = type.CreateUsingDeserializationConstructor<BusinessObjectBase>();
businessObject.Initialize(true);
return businessObject;
};
}
return contract;
}
}
I hope this helps someone.
Best regards,
Carsten

Categories