Json.Net Self Recurse on Custom Converter - c#

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.

Related

Newtonsoft.Json - how can I choose the constructor to deserialize based on passed object properties types?

I have a generic type with 2 constructor each accepting 1 parameter with different types.
There is no problem with serialization. But when I try to deserialize, I get an error that Newtonsoft is Unable to find a constructor to use for this type.
How can I override Newtonsoft's default behavious and choose the constructor to be used for deserialization based on the property types?
I DO NOT have access to the Test class, so I can not change it in any way.
I wish to use another way not write a an entirely custom JsonConverter.
I saw this answer here that you can override the DefaultContractResolver. It works well to choose a different constructor, but I can not see how I can access the properties of the object I am trying to deserialize?
How can I choose a constructor based on the properties types of the object to be deserialized?
public class Test<T, U> where U : struct
{
public Test(T firstProperty)
{
FirstProperty = firstProperty;
}
public Test(U secondProperty)
{
SecondProperty = secondProperty;
}
public T FirstProperty { get; }
public U SecondProperty { get; }
}
There is no way to configure Json.NET to choose a constructor based on the presence or absence of certain properties in the JSON to be deserialized. It simply isn't implemented.
As a workaround, you can create a custom JsonConverter<Test<T, U>> that deserializes to some intermediate DTO that tracks the presence of both properties, and then chooses the correct constructor afterwards. Then you can create a custom contract resolver that applies the converter to all concrete types Test<T, U>.
The following converter and contract resolver perform this task:
class TestConverter<T, U> : JsonConverter where U : struct
{
// Here we make use of the {PropertyName}Specified pattern to track which properties actually got deserialized.
// https://stackoverflow.com/questions/39223335/how-to-force-newtonsoft-json-to-serialize-all-properties-strange-behavior-with/
class TestDTO
{
public T FirstProperty { get; set; }
[JsonIgnore] public bool FirstPropertySpecified { get; set; }
public U SecondProperty { get; set; }
[JsonIgnore] public bool SecondPropertySpecified { get; set; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var dto = serializer.Deserialize<TestDTO>(reader);
if (dto == null)
return null;
else if (dto.FirstPropertySpecified && !dto.SecondPropertySpecified)
return new Test<T, U>(dto.FirstProperty);
else if (!dto.FirstPropertySpecified && dto.SecondPropertySpecified)
return new Test<T, U>(dto.SecondProperty);
else
throw new InvalidOperationException(string.Format("Wrong number of properties specified for {0}", objectType));
}
public override bool CanConvert(Type objectType) => objectType == typeof(Test<T, U>);
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
public class TestContractResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Test<,>))
contract.Converter = (JsonConverter)Activator.CreateInstance(typeof(TestConverter<,>).MakeGenericType(objectType.GetGenericArguments()));
return contract;
}
}
Then use them e.g. as follows:
var json1 = #"{""FirstProperty"":""hello""}";
var json2 = #"{""SecondProperty"": 10101}";
IContractResolver resolver = new TestContractResolver(); // Cache this statically for best performance
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var test1 = JsonConvert.DeserializeObject<Test<string, long>>(json1, settings);
Assert.AreEqual(test1.FirstProperty, "hello");
var test2 = JsonConvert.DeserializeObject<Test<string, long>>(json2, settings);
Assert.AreEqual(test2.SecondProperty, 10101L);
Notes:
The converter throws an InvalidOperationException if the JSON does not contain exactly one of the two properties.
Feel free to modify this as per your requirements.
The converter does not implement serialization as your Test<T, U> type does not provide a method to track which property was initialized.
The converter does not attempt to handle subclasses of Test<T, U>.
Demo fiddle here.

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

JSON.NET - Make attributes to check for empty string property values

With an attribute [JsonProperty(Required = Required.Always)] annotating my model I am able to check, that the property value is not null.
For strings I would like to check for empty values as well. Is there a way to check if string property value is empty while using attributes in Json.NET? Or is there any other way?
This JSON should cause a problem
{
"Property1": "",
...
}
By using a JSON schema you can define minimum lengths for specific properties, among other things. This is reasonably simple using the additional Json.NET Schema package, after slight modifications to the deserialization code to specify the schema to be used for validation. However, the Json.NET Schema library is not MIT licensed and uses restrictive/commercial licensing unlike Json.NET itself. There may be other implementations, though.
In a similar case I ended up (ab)using a JsonConverter to do validation instead. This is somewhat hacky as it's not really a converter, but the convenient thing is that it works purely by applying a Json.NET attribute to the model without any other code changes.
public class NonEmptyStringConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(string);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> throw new NotImplementedException();
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.String)
throw CreateException($"Expected string value, but found {reader.TokenType}.", reader);
var value = (string)reader.Value;
if (String.IsNullOrEmpty(value))
throw CreateException("Non-empty string required.", reader);
return value;
}
private static Exception CreateException(string message, JsonReader reader)
{
var info = (IJsonLineInfo)reader;
return new JsonSerializationException(
$"{message} Path '{reader.Path}', line {info.LineNumber}, position {info.LinePosition}.",
reader.Path, info.LineNumber, info.LinePosition, null);
}
}
Usage:
[JsonProperty(Required = Required.Always)]
[JsonConverter(typeof(NonEmptyStringConverter))]
public string Property1 { get; set; }
The Required.Always should still be applied to handle cases where the property is missing completely, in which case the converter won't be called by Json.NET.

How Mock JsonReader unit testing a custom JsonConverter

Wrote a Custom JsonConverter to handle different Json formats that are returned by different versions of the same api. One app makes a request to several other apps, and we dont know which format will be returned so the JsonConverter handles this and seems to work well. I need to add unit tests to the project, except I have not found helpful resources to help Mock out some of the Newtonsoft.Json objects, mainly JsonReader.
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jsonValue = JObject.Load(reader);
if(jsonValue == null)
{
return null;
}
var responseData = ReadJsonObject(jsonValue);
return responseData;
}
[TestMethod]
public void ReadJsonReturnNullForNullJson()
{
var converter = new DataConverter();
_mockJsonReader.Setup(x => x.Value).Returns(null);
var responseData = converter.ReadJson(_mockJsonReader.Object, typeof(ProbeResponseData), null, _mockJsonSerializer.Object);
Assert.IsNull(responseData);
}
Some code has been taken out of the ReadJson method. I am trying Setup the JsonReader to return the value of the actual json, in this case a null value but in other unit tests I would want an actual Json(JObject). When running the unit test I receive a "Newtonsoft.JsonReaderException: Error reading JObject from JsonReader. Path ''."
The use of DeserializeObject<T> will call your override of ReadJson under the hood.
[TestMethod]
public void ReadJsonVerifyTypeReturned()
{
var testJson = CreateJsonString();
var result = JsonConvert.DeserializeObject<ProbeResponseData>(testJson);
var resultCheck = result as ProbeResponseData;
Assert.IsNotNull(resultCheck);
}
Whilst using JsonConvert or JsonSerializer directly will allow you to test it, you probably should make your converter tests a little more direct. For instance, you can't guarantee that JSON.NET will do what you expect when you call the deserializer, whereas what you actually want to test is your custom converter - what JSON.NET does with that is out of your control.
Consider this example:
public readonly struct UserId
{
public static readonly UserId Empty = new UserId();
public UserId(int value)
{
Value = value;
HasValue = true;
}
public int Value { get; }
public bool HasValue { get; }
}
I've got this struct, which is backed by an int. I want to deserialize a specific JSON number value as an int -> UserId. So, I create a custom converter:
public class UserIdConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(UserId);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
int? id = serializer.Deserialize<int?>(reader);
if (!id.HasValue)
{
return UserId.Empty;
}
return new UserId(id.Value);
}
}
I've skipped over the implementation of WriteJson in this instance, but the logic is the same.
I would write my test as follows:
[Fact]
public void UserIdJsonConverter_CanConvertFromJsonNumber()
{
// Arrange
var serialiser = new JsonSerializer();
var reader = CreateJsonReader("10");
var converter = new UserIdJsonConverter();
// Act
var result = converter.ReadJson(reader, typeof(UserId), null, serialiser);
// Assert
Assert.NotNull(result);
Assert.IsType<UserId>(result);
var id = (UserId)result;
Assert.True(id.HasValue);
Assert.Equal(10, id.Value);
}
private JsonTextReader CreateJsonReader(string json)
=> new JsonTextReader(new StringReader(json));
In doing so, I can create a test purely around my ReadJson method, and confirm it does what I expect. Going further, I could potentially mock elements, such as the JsonReader and JsonSerializer to result in different preconditions so I can test a wide array of scenarios.
The issue with relying on JsonConvert or JsonSerializer to run the full deserialization process, is that you're introducing other logic which is largely outside of your control. I.e., what if through deserialization, JSON.NET actually makes a different decision and your custom converter is never used - your test isn't responsible for testing JSON.NET itself, but what your custom converter actually does.
Without knowing the inner workings of your custom converter, could you just create a test double instead of a mock?
public class JsonReaderThatReturnsNull : JsonReader
{
public override bool Read()
{
return true;
}
public override object Value => null;
}
To get to test the converter, it needs to be in the list of converters. If you used JsonConverterAttribute, it will work automatigically, but if you didn't you can do it like this:
var serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new MyJsonConverter());
var serializer = JsonSerializer.Create(serializerSettings);
var jObject = JObject.Parse(myString);
var result = jObject.ToObject<MyObject>(serializer);
Try to use this to check converted result.
DeserializedType result = JsonConvert.DeserializeObject<DeserializedType>(json, new Converter(parms));
From Documentation

Registering a custom JsonConverter globally in Json.Net

Using Json.Net, I have properties in my objects which need special care in order to serialize / deserialize them. Making a descendant of JsonConverter, I managed to accomplish this successfully. This is the common way of doing this:
public class SomeConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
...
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
...
}
public override bool CanConvert(Type objectType)
{
...
}
}
class SomeClass
{
[JsonProperty, JsonConverter(typeof(SomeConverter))]
public SomeType SomeProperty;
}
//Later on, in code somewhere
SomeClass SomeObject = new SomeClass();
string json = JsonConvert.SerializeObject(SomeObject, new SomeConverter());
My problem with this code is that I need to introduce my custom converter in every serialization / deserialization. In my project there are many cases that I cannot do that. For instance, I'm using other external projects which make use of Json.Net as well and they will be working on my SomeClass instances. But since I don't want to or can't make change in their code, I have no way to introduce my converter.
Is there any way I can register my converter, using some static member perhaps, in Json.Net so no matter where serialization / deserialization happens, my converter is always present?
Yes, this is possible using Json.Net 5.0.5 or later. See JsonConvert.DefaultSettings.
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new SomeConverter() }
};
// Later on...
string json = JsonConvert.SerializeObject(someObject); // this will use SomeConverter
If you're using Web API, you can set up a converter globally like this instead:
var config = GlobalConfiguration.Configuration;
var jsonSettings = config.Formatters.JsonFormatter.SerializerSettings;
jsonSettings.Converters.Add(new SomeConverter());
Another approach (which wins in priority over the one #Brian mentions above) is to implement a custom contract resolver
JsonFormatter.SerializerSettings.ContractResolver = new CustomContractResolver();
And the implementation is rather straightforward
public class CustomContractResolver : DefaultContractResolver
{
private static readonly JsonConverter _converter = new MyCustomConverter();
private static Type _type = typeof (MyCustomType);
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (objectType == null || !_type.IsAssignableFrom(objectType)) // alternatively _type == objectType
{
return base.ResolveContractConverter(objectType);
}
return _converter;
}
}
Both methods are valid, this one is just a bigger hammer
This approach from the question ASP.NET Web API Custom JsonConverter is never called works with Web API:
// Add a custom converter for Entities.
foreach (var formatter in GlobalConfiguration.Configuration.Formatters)
{
var jsonFormatter = formatter as JsonMediaTypeFormatter;
if (jsonFormatter == null)
continue;
jsonFormatter.SerializerSettings.Converters.Add(new MyConverter());
}
Just put it somewhere into Global.asax.
The other answers didn't work for me. DefaultSettings has no effect on Web API actions, and the JsonFormatter configuration property does not seem to exist in the .NET framework version I use.

Categories