Custom JSON.Net serializer for generic classes [duplicate] - c#

I have a generic type that wraps a single primitive type to give it value equality semantics
public class ValueObject<T>
{
public T Value { get; }
public ValueObject(T value) => Value = value;
// various other equality members etc...
}
It is used like:
public class CustomerId : ValueObject<Guid>
{
public CustomerId(Guid value) : base(value) { }
}
public class EmailAddress : ValueObject<string>
{
public EmailAddress(string value) : base(value) { }
}
The issue is when serializing a type like:
public class Customer
{
public CustomerId Id { get; }
public EmailAddress Email { get; }
public Customer(CustomerId id, EmailAddress email)
{
Id = id;
Email = email;
}
}
Each object the inherits from ValueObject<T> is wrapped in a Value property (as expected). For example
var customerId = new CustomerId(Guid.NewGuid());
var emailAddress = new EmailAddress("some#email.com");
var customer = new Customer(customerId, emailAddress);
var customerAsJson = JsonConvert.SerializeObject(customer, Formatting.Indented, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
})
Results in
{
"id": {
"value": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c"
},
"email": {
"value": "some#email.com"
}
}
Is there a way to write a custom JsonConverter so the the Value property is excluded for types subclassing ValueObject<T> so that the above example would output
{
"id": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c",
"email": "some#email.com"
}
I would prefer to have a single JsonConverter that can handle all ValueObject<T> rather than having to define a separate JsonConverter for each ValueObject<T> subclass
My first attempt was
public class ValueObjectOfTConverter : JsonConverter
{
private static readonly Type ValueObjectGenericType = typeof(ValueObject<>);
private static readonly string ValuePropertyName = nameof(ValueObject<object>.Value);
public override bool CanConvert(Type objectType) =>
IsSubclassOfGenericType(objectType, ValueObjectGenericType);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// converts "f5ce21a5-a0d1-4888-8d22-6f484794ac7c" => "value": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c"
var existingJsonWrappedInValueProperty = new JObject(new JProperty(ValuePropertyName, JToken.Load(reader)));
return existingJsonWrappedInValueProperty.ToObject(objectType, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// to implement
}
private static bool IsSubclassOfGenericType(Type typeToCheck, Type openGenericType)
{
while (typeToCheck != null && typeToCheck != typeof(object))
{
var cur = typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck;
if (openGenericType == cur) return true;
typeToCheck = typeToCheck.BaseType;
}
return false;
}
}

You can do this with a custom JsonConverter similar to the ones shown in Json.Net: Serialize/Deserialize property as a value, not as an object. However, since ValueObject<T> does not have a non-generic method to get and set the Value as an object, you will need to use reflection.
Here's one approach:
class ValueConverter : JsonConverter
{
static Type GetValueType(Type objectType)
{
return objectType
.BaseTypesAndSelf()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueObject<>))
.Select(t => t.GetGenericArguments()[0])
.FirstOrDefault();
}
public override bool CanConvert(Type objectType)
{
return GetValueType(objectType) != null;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// You need to decide whether a null JSON token results in a null ValueObject<T> or
// an allocated ValueObject<T> with a null Value.
if (reader.SkipComments().TokenType == JsonToken.Null)
return null;
var valueType = GetValueType(objectType);
var value = serializer.Deserialize(reader, valueType);
// Here we assume that every subclass of ValueObject<T> has a constructor with a single argument, of type T.
return Activator.CreateInstance(objectType, value);
}
const string ValuePropertyName = nameof(ValueObject<object>.Value);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
var valueProperty = contract.Properties.Where(p => p.UnderlyingName == ValuePropertyName).Single();
// You can simplify this to .Single() if ValueObject<T> has no other properties:
// var valueProperty = contract.Properties.Single();
serializer.Serialize(writer, valueProperty.ValueProvider.GetValue(value));
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
}
You could then apply the converter directly to ValueType<T> like so:
[JsonConverter(typeof(ValueConverter))]
public class ValueObject<T>
{
// Remainder unchanged
}
Or apply it in settings instead:
var settings = new JsonSerializerSettings
{
Converters = { new ValueConverter() },
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var customerAsJson = JsonConvert.SerializeObject(customer, Formatting.Indented, settings);
Working sample .Net fiddle #1 here.
Alternatively, you might consider adding a non-generic method to access the value as an object, e.g. like so:
public interface IHasValue
{
object GetValue(); // A method rather than a property to ensure the non-generic value is never serialized directly.
}
public class ValueObject<T> : IHasValue
{
public T Value { get; }
public ValueObject(T value) => Value = value;
// various other equality members etc...
#region IHasValue Members
object IHasValue.GetValue() => Value;
#endregion
}
With this addition, WriteJson() becomes much simpler:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, ((IHasValue)value).GetValue());
}
Working sample .Net fiddle #2 here.
Notes:
ReadJson() assumes that every subclass of Value<T> has a public constructor taking a single argument of type T.
Applying the converter directly to ValueType<T> using [JsonConverter(typeof(ValueConverter))] will have slightly better performance, since CanConvert need never get called. See Performance Tips: JsonConverters for details.
You need to decide how to handle a null JSON token. Should it result in a null ValueType<T>, or an allocated ValueType<T> with a null Value?
In the second version of ValueType<T> I implemented IHasValue.GetValue() explicitly to discourage its use in cases where an instance of ValueType<T> is used in statically typed code.
If you really only want to apply the converter to types subclassing ValueObject<T> and not ValueObject<T> itself, in GetValueType(Type objectType) add a call to .Skip(1):
static Type GetValueType(Type objectType)
{
return objectType
.BaseTypesAndSelf()
.Skip(1) // Do not apply the converter to ValueObject<T> when not subclassed
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueObject<>))
.Select(t => t.GetGenericArguments()[0])
.FirstOrDefault();
}
And then apply the converter in JsonSerializerSettings.Converters rather than directly to ValueObject<T>.

Related

Adding parameter to Json converter during deserialization with converter defined for property

I would like to pass a parameter to the Json converter at the time of deserialization. At the same time, I would like the converter to execute only for the properties indicated by the attribute.
public class Contract
{
[JsonConverter(typeof(MyJsonConverter))]
public string Property { get; set; }
}
string parameter = "value";
var jsonSerializerSettings = new JsonSerializerSettings
{
Converters = { new MyJsonConverter(parameter) },
};
var contract = JsonConvert.DeserializeObject<Contract>(json, jsonSerializerSettings);
public class MyJsonConverter : JsonConverter
{
private readonly string _parameter;
public MyJsonConverter(string parameter)
{
_parameter = parameter;
}
public override bool CanConvert(Type objectType)
{
//
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
// use _parameter here
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
//
}
}
I know that the JsonConverter attribute accepts parameters for the converter, but then I would have to add one parameter to the Contract class permanently.
[JsonConverter(typeof(MyJsonConverter), <parameters>)]
I would like the parameters to be dynamically provided at the time of deserialization - how do I achieve this?
You can use StreamingContext.Context from JsonSerializerSettings.Context to pass data into a JsonConverter.
First, define the following interface and classes to cache data, keyed by System.Type, inside a StreamingContext:
public static class StreamingContextExtensions
{
public static StreamingContext AddTypeData(this StreamingContext context, Type type, object? data)
{
var c = context.Context;
IStreamingContextTypeDataDictionary dictionary;
if (context.Context == null)
dictionary = new StreamingContextTypeDataDictionary();
else if (context.Context is IStreamingContextTypeDataDictionary d)
dictionary = d;
else
throw new InvalidOperationException(string.Format("context.Context is already populated with {0}", context.Context));
dictionary.AddData(type, data);
return new StreamingContext(context.State, dictionary);
}
public static bool TryGetTypeData(this StreamingContext context, Type type, out object? data)
{
IStreamingContextTypeDataDictionary? dictionary = context.Context as IStreamingContextTypeDataDictionary;
if (dictionary == null)
{
data = null;
return false;
}
return dictionary.TryGetData(type, out data);
}
}
public interface IStreamingContextTypeDataDictionary
{
public void AddData(Type type, object? data);
public bool TryGetData(Type type, out object? data);
}
class StreamingContextTypeDataDictionary : IStreamingContextTypeDataDictionary
{
readonly Dictionary<Type, object?> dictionary = new ();
public void AddData(Type type, object? data) => dictionary.Add(type, data);
public bool TryGetData(Type type, out object? data) => dictionary.TryGetValue(type, out data);
}
Then rewrite MyConverter as follows:
public class MyJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(string);
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
// Grab parameter from serializer.Context. Use some default value (here "") if not present.
var _parameter = serializer.Context.TryGetTypeData(typeof(MyJsonConverter), out var s) ? (string?)s : "";
// Use _parameter as required, e.g.
return _parameter + (string?)JToken.Load(reader);
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) =>
writer.WriteValue((string)value!);
}
And you will be able to deserialize as follows:
var _parameter = "my runtime parameter: ";
var settings = new JsonSerializerSettings
{
Context = new StreamingContext(StreamingContextStates.All)
.AddTypeData(typeof(MyJsonConverter), _parameter),
// Add any other required customizations,
};
var contract = JsonConvert.DeserializeObject<Contract>(json, settings);
Notes:
The data cached inside the StreamingContext is keyed by type so that multiple converters could access cached data inside without interfering with each other. The type used should be the converter type, not the property type.
Demo fiddle #1 here.
Honestly though I don't recommend this design. StreamingContext is unfamiliar to current .NET programmers (it's a holdover from binary serialization) and it feels completely surprising to use it to pass data deep down into some JsonConverter.ReadJson() method.
As an alternative, you might consider creating a custom contract resolver that replaces the default MyJsonConverter applied at compile time with a different instance that has the required parameters.
First, define the following contract resolver:
public class ConverterReplacingContractResolver : DefaultContractResolver
{
readonly Dictionary<(Type type, string name), JsonConverter?> replacements;
public ConverterReplacingContractResolver(IEnumerable<KeyValuePair<(Type type, string name), JsonConverter?>> replacements) =>
this.replacements = (replacements ?? throw new ArgumentNullException()).ToDictionary(r => r.Key, r => r.Value);
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (member.DeclaringType != null && replacements.TryGetValue((member.DeclaringType, member.Name), out var converter))
property.Converter = converter;
return property;
}
}
Then modify MyJsonConverter so it has a default constructor with a default value for _parameter:
public class MyJsonConverter : JsonConverter
{
private readonly string _parameter;
public MyJsonConverter() : this("") { }
public MyJsonConverter(string parameter) => this._parameter = parameter;
public override bool CanConvert(Type objectType) => objectType == typeof(string);
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) =>
_parameter + (string?)JToken.Load(reader);
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) =>
writer.WriteValue((string)value!);
}
And now you will be able to deserialize as follows:
var _parameter = "my runtime parameter: ";
var replacementsConverters = new KeyValuePair<(Type type, string name), JsonConverter?> []
{
new((typeof(Contract), nameof(Contract.Property)), new MyJsonConverter(_parameter)),
};
var resolver = new ConverterReplacingContractResolver(replacementsConverters)
{
// Add any other required customizations, e.g.
//NamingStrategy = new CamelCaseNamingStrategy()
};
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
// Add other settings as required,
};
var contract = JsonConvert.DeserializeObject<Contract>(json, settings);
Demo fiddle #2 here.

json.net serialize derived as generic base

(this issue stems from trying to serialize/deserialize LikeType classes to JSON - https://github.com/kleinwareio/LikeType)
I have:
public abstract class LikeType<T>
{
public T Value;
// ....
// how to tell json.net to serialize/deserialize classes deriving
// from this like it would T ???
}
public class Name : LikeType<string> {
public Name(string s) : base(s) { }
// does not add any properties
}
void test()
{
var name = new Name("john");
var jobj = new JObject();
try
{
jobj.Add("key", new JObject(name));
}
catch (Exception e)
{
!Exeption !
e = {System.ArgumentException: Could not determine JSON object type for type Name. at Newtonsoft.Json.Linq.JValue.GetValueType(Nullable`1 current, Object value) at Newtonsoft.Json.Linq.JContainer.CreateFromContent(Object content)
}
}
How can I specify that all classes deriving from LikeType<T> will be serialized/ deserialized to JSON with Json.Net in the same way T would?
(in this case, Json.Net should serialize/deserialize Name in the same way it would a string)
I believe you want to "forward" LikeType<T> serialization, treating this like an invisible wrapper type. This assumption is crucial to my solution.
I'd suggest using JsonConverter implementation to do that. There is a very similar post here: Json.NET - Serialize generic type wrapper without property name
I've adapted the example to your case. This is the adapted approach:
class LikeTypeConverter : JsonConverter
{
static Type GetValueType(Type objectType)
{
return objectType
.BaseTypesAndSelf()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(LikeType<>))
.Select(t => t.GetGenericArguments()[0])
.FirstOrDefault();
}
public override bool CanConvert(Type objectType)
{
return GetValueType(objectType) != null;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// You need to decide whether a null JSON token results in a null LikeType<T> or
// an allocated LikeType<T> with a null Value.
if (reader.SkipComments().TokenType == JsonToken.Null)
return null;
var valueType = GetValueType(objectType);
var value = serializer.Deserialize(reader, valueType);
// Here we assume that every subclass of LikeType<T> has a constructor with a single argument, of type T.
return Activator.CreateInstance(objectType, value);
}
const string ValuePropertyName = "Value";// nameof(LikeType<object>.Value); // in C#6+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
var valueProperty = contract.Properties.Single(p => p.UnderlyingName == ValuePropertyName);
serializer.Serialize(writer, valueProperty.ValueProvider.GetValue(value));
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
{
}
return reader;
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
}
You can use this as an attribute on LikeType<T> declaration if you want to include this in your library:
[JsonConverter(typeof(LikeTypeConverter))]
public abstract class LikeType<T> { ... }
Or you can use the converter when necessary, modifying JsonSerializerSettings.Converters collection:
var settings = new JsonSerializerSettings
{
Converters = { new LikeTypeConverter() },
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var result = JsonConvert.SerializeObject(myObject, Formatting.Indented, settings);
I've also created a working dotnetfiddle sample for demonstration (also adapting the one from linked post).
One way of controlling what most serializers serialize is using the Serializable attribute and implement the ISerializable interface.

Json.NET - Serialize generic type wrapper without property name

I have a generic type that wraps a single primitive type to give it value equality semantics
public class ValueObject<T>
{
public T Value { get; }
public ValueObject(T value) => Value = value;
// various other equality members etc...
}
It is used like:
public class CustomerId : ValueObject<Guid>
{
public CustomerId(Guid value) : base(value) { }
}
public class EmailAddress : ValueObject<string>
{
public EmailAddress(string value) : base(value) { }
}
The issue is when serializing a type like:
public class Customer
{
public CustomerId Id { get; }
public EmailAddress Email { get; }
public Customer(CustomerId id, EmailAddress email)
{
Id = id;
Email = email;
}
}
Each object the inherits from ValueObject<T> is wrapped in a Value property (as expected). For example
var customerId = new CustomerId(Guid.NewGuid());
var emailAddress = new EmailAddress("some#email.com");
var customer = new Customer(customerId, emailAddress);
var customerAsJson = JsonConvert.SerializeObject(customer, Formatting.Indented, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
})
Results in
{
"id": {
"value": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c"
},
"email": {
"value": "some#email.com"
}
}
Is there a way to write a custom JsonConverter so the the Value property is excluded for types subclassing ValueObject<T> so that the above example would output
{
"id": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c",
"email": "some#email.com"
}
I would prefer to have a single JsonConverter that can handle all ValueObject<T> rather than having to define a separate JsonConverter for each ValueObject<T> subclass
My first attempt was
public class ValueObjectOfTConverter : JsonConverter
{
private static readonly Type ValueObjectGenericType = typeof(ValueObject<>);
private static readonly string ValuePropertyName = nameof(ValueObject<object>.Value);
public override bool CanConvert(Type objectType) =>
IsSubclassOfGenericType(objectType, ValueObjectGenericType);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// converts "f5ce21a5-a0d1-4888-8d22-6f484794ac7c" => "value": "f5ce21a5-a0d1-4888-8d22-6f484794ac7c"
var existingJsonWrappedInValueProperty = new JObject(new JProperty(ValuePropertyName, JToken.Load(reader)));
return existingJsonWrappedInValueProperty.ToObject(objectType, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// to implement
}
private static bool IsSubclassOfGenericType(Type typeToCheck, Type openGenericType)
{
while (typeToCheck != null && typeToCheck != typeof(object))
{
var cur = typeToCheck.IsGenericType ? typeToCheck.GetGenericTypeDefinition() : typeToCheck;
if (openGenericType == cur) return true;
typeToCheck = typeToCheck.BaseType;
}
return false;
}
}
You can do this with a custom JsonConverter similar to the ones shown in Json.Net: Serialize/Deserialize property as a value, not as an object. However, since ValueObject<T> does not have a non-generic method to get and set the Value as an object, you will need to use reflection.
Here's one approach:
class ValueConverter : JsonConverter
{
static Type GetValueType(Type objectType)
{
return objectType
.BaseTypesAndSelf()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueObject<>))
.Select(t => t.GetGenericArguments()[0])
.FirstOrDefault();
}
public override bool CanConvert(Type objectType)
{
return GetValueType(objectType) != null;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// You need to decide whether a null JSON token results in a null ValueObject<T> or
// an allocated ValueObject<T> with a null Value.
if (reader.SkipComments().TokenType == JsonToken.Null)
return null;
var valueType = GetValueType(objectType);
var value = serializer.Deserialize(reader, valueType);
// Here we assume that every subclass of ValueObject<T> has a constructor with a single argument, of type T.
return Activator.CreateInstance(objectType, value);
}
const string ValuePropertyName = nameof(ValueObject<object>.Value);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
var valueProperty = contract.Properties.Where(p => p.UnderlyingName == ValuePropertyName).Single();
// You can simplify this to .Single() if ValueObject<T> has no other properties:
// var valueProperty = contract.Properties.Single();
serializer.Serialize(writer, valueProperty.ValueProvider.GetValue(value));
}
}
public static partial class JsonExtensions
{
public static JsonReader SkipComments(this JsonReader reader)
{
while (reader.TokenType == JsonToken.Comment && reader.Read())
;
return reader;
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
}
You could then apply the converter directly to ValueType<T> like so:
[JsonConverter(typeof(ValueConverter))]
public class ValueObject<T>
{
// Remainder unchanged
}
Or apply it in settings instead:
var settings = new JsonSerializerSettings
{
Converters = { new ValueConverter() },
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var customerAsJson = JsonConvert.SerializeObject(customer, Formatting.Indented, settings);
Working sample .Net fiddle #1 here.
Alternatively, you might consider adding a non-generic method to access the value as an object, e.g. like so:
public interface IHasValue
{
object GetValue(); // A method rather than a property to ensure the non-generic value is never serialized directly.
}
public class ValueObject<T> : IHasValue
{
public T Value { get; }
public ValueObject(T value) => Value = value;
// various other equality members etc...
#region IHasValue Members
object IHasValue.GetValue() => Value;
#endregion
}
With this addition, WriteJson() becomes much simpler:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, ((IHasValue)value).GetValue());
}
Working sample .Net fiddle #2 here.
Notes:
ReadJson() assumes that every subclass of Value<T> has a public constructor taking a single argument of type T.
Applying the converter directly to ValueType<T> using [JsonConverter(typeof(ValueConverter))] will have slightly better performance, since CanConvert need never get called. See Performance Tips: JsonConverters for details.
You need to decide how to handle a null JSON token. Should it result in a null ValueType<T>, or an allocated ValueType<T> with a null Value?
In the second version of ValueType<T> I implemented IHasValue.GetValue() explicitly to discourage its use in cases where an instance of ValueType<T> is used in statically typed code.
If you really only want to apply the converter to types subclassing ValueObject<T> and not ValueObject<T> itself, in GetValueType(Type objectType) add a call to .Skip(1):
static Type GetValueType(Type objectType)
{
return objectType
.BaseTypesAndSelf()
.Skip(1) // Do not apply the converter to ValueObject<T> when not subclassed
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueObject<>))
.Select(t => t.GetGenericArguments()[0])
.FirstOrDefault();
}
And then apply the converter in JsonSerializerSettings.Converters rather than directly to ValueObject<T>.

How to convert only given type even if it is stored as object? [duplicate]

Suppose I have a class like this:
public class Example {
public int TypedProperty { get; set; }
public object UntypedProperty { get; set; }
}
And suppose someone comes along and writes:
var example = new Example
{
TypedProperty = 5,
UntypedProperty = Guid.NewGuid()
}
If I serialize this with JsonConvert.SerializeObject(example), I get
{
"TypedProperty": 5,
"UntypedProperty": "24bd733f-2ade-4374-9db6-3c9f3d97b12c"
}
Ideally, I'd like to get something like this:
{
"TypedProperty": 5,
"UntypedProperty":
{
"$type": "System.Guid,mscorlib",
"$value": "24bd733f-2ade-4374-9db6-3c9f3d97b12c"
}
}
But TypeNameHandling doesn't work in this scenario. How can I (de)serialize an untyped property?
If you serialize your class with TypeNameHandling.All or TypeNameHandling.Auto,
then when the UntypedProperty property would be serialized as a JSON container (either an object or array) Json.NET should correctly serialize and deserialize it by storing type information in the JSON file in a "$type" property. However, in cases where UntypedProperty is serialized as a JSON primitive (a string, number, or Boolean) this doesn't work because, as you have noted, a JSON primitive has no opportunity to include a "$type" property.
The solution is, when serializing a type with a property of type object, to serialize wrappers classes for primitive values that can encapsulate the type information, along the lines of this answer. Here is a custom JSON converter that injects such a wrapper:
public class UntypedToTypedValueConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException("This converter should only be applied directly via ItemConverterType, not added to JsonSerializer.Converters");
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var value = serializer.Deserialize(reader, objectType);
if (value is TypeWrapper)
{
return ((TypeWrapper)value).ObjectValue;
}
return value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (serializer.TypeNameHandling == TypeNameHandling.None)
{
Console.WriteLine("ObjectItemConverter used when serializer.TypeNameHandling == TypeNameHandling.None");
serializer.Serialize(writer, value);
}
// Handle a couple of simple primitive cases where a type wrapper is not needed
else if (value is string)
{
writer.WriteValue((string)value);
}
else if (value is bool)
{
writer.WriteValue((bool)value);
}
else
{
var contract = serializer.ContractResolver.ResolveContract(value.GetType());
if (contract is JsonPrimitiveContract)
{
var wrapper = TypeWrapper.CreateWrapper(value);
serializer.Serialize(writer, wrapper, typeof(object));
}
else
{
serializer.Serialize(writer, value);
}
}
}
}
abstract class TypeWrapper
{
protected TypeWrapper() { }
[JsonIgnore]
public abstract object ObjectValue { get; }
public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}
sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }
public TypeWrapper(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
public T Value { get; set; }
}
Then apply it to your type using [JsonConverter(typeof(UntypedToTypedValueConverter))]:
public class Example
{
public int TypedProperty { get; set; }
[JsonConverter(typeof(UntypedToTypedValueConverter))]
public object UntypedProperty { get; set; }
}
If you cannot modify the Example class in any way to add this attribute (your comment The class isn't mine to change suggests as much) you could inject the converter with a custom contract resolver:
public class UntypedToTypedPropertyContractResolver : DefaultContractResolver
{
readonly UntypedToTypedValueConverter converter = new UntypedToTypedValueConverter();
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
// See also https://stackoverflow.com/questions/33557737/does-json-net-cache-types-serialization-information
static UntypedToTypedPropertyContractResolver instance;
// Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
static UntypedToTypedPropertyContractResolver() { instance = new UntypedToTypedPropertyContractResolver(); }
public static UntypedToTypedPropertyContractResolver Instance { get { return instance; } }
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
foreach (var property in contract.Properties.Concat(contract.CreatorParameters))
{
if (property.PropertyType == typeof(object)
&& property.Converter == null)
{
property.Converter = property.MemberConverter = converter;
}
}
return contract;
}
}
And use it as follows:
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
ContractResolver = UntypedToTypedPropertyContractResolver.Instance,
};
var json = JsonConvert.SerializeObject(example, Formatting.Indented, settings);
var example2 = JsonConvert.DeserializeObject<Example>(json, settings);
In both cases the JSON created looks like:
{
"TypedProperty": 5,
"UntypedProperty": {
"$type": "Question38777588.TypeWrapper`1[[System.Guid, mscorlib]], Tile",
"Value": "e2983c59-5ec4-41cc-b3fe-34d9d0a97f22"
}
}
Lookup SerializeWithJsonConverters.htm and ReadingWritingJSON.
Call: JsonConvert.SerializeObject(example, new ObjectConverter());
class ObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(Example);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Example e = (Example)value;
writer.WriteStartObject();
writer.WritePropertyName("TypedProperty");
writer.WriteValue(e.TypedProperty);
writer.WritePropertyName("UntypedProperty");
writer.WriteStartObject();
writer.WritePropertyName("$type");
writer.WriteValue(e.UntypedProperty.GetType().FullName);
writer.WritePropertyName("$value");
writer.WriteValue(e.UntypedProperty.ToString());
writer.WriteEndObject();
writer.WriteEndObject();
}
}

JSON.Net throws StackOverflowException when using [JsonConvert()]

I wrote this simple code to Serialize classes as flatten, but when I use [JsonConverter(typeof(FJson))] annotation, it throws a StackOverflowException. If I call the SerializeObject manually, it works fine.
How can I use JsonConvert in Annotation mode:
class Program
{
static void Main(string[] args)
{
A a = new A();
a.id = 1;
a.b.name = "value";
string json = null;
// json = JsonConvert.SerializeObject(a, new FJson()); without [JsonConverter(typeof(FJson))] annotation workd fine
// json = JsonConvert.SerializeObject(a); StackOverflowException
Console.WriteLine(json);
Console.ReadLine();
}
}
//[JsonConverter(typeof(FJson))] StackOverflowException
public class A
{
public A()
{
this.b = new B();
}
public int id { get; set; }
public string name { get; set; }
public B b { get; set; }
}
public class B
{
public string name { get; set; }
}
public class FJson : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = JToken.FromObject(value);
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}
JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}
private void WriteJson(JsonWriter writer, JObject value)
{
foreach (var p in value.Properties())
{
if (p.Value is JObject)
WriteJson(writer, (JObject)p.Value);
else
p.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true; // works for any type
}
}
After reading (and testing) Paul Kiar & p.kaneman solution I'd say it seems to be a challenging task to implement WriteJson. Even though it works for the most cases - there are a few edge cases that are not covered yet.
Examples:
public bool ShouldSerialize*() methods
null values
value types (struct)
json converter attributes
..
Here is (just) another try:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
if (ReferenceEquals(value, null)) {
writer.WriteNull();
return;
}
var contract = (JsonObjectContract)serializer
.ContractResolver
.ResolveContract(value.GetType());
writer.WriteStartObject();
foreach (var property in contract.Properties) {
if (property.Ignored) continue;
if (!ShouldSerialize(property, value)) continue;
var property_name = property.PropertyName;
var property_value = property.ValueProvider.GetValue(value);
writer.WritePropertyName(property_name);
if (property.Converter != null && property.Converter.CanWrite) {
property.Converter.WriteJson(writer, property_value, serializer);
} else {
serializer.Serialize(writer, property_value);
}
}
writer.WriteEndObject();
}
private static bool ShouldSerialize(JsonProperty property, object instance) {
return property.ShouldSerialize == null
|| property.ShouldSerialize(instance);
}
Json.NET does not have convenient support for converters that call JToken.FromObject to generate a "default" serialization and then modify the resulting JToken for output - precisely because the StackOverflowException due to recursive calls to JsonConverter.WriteJson() that you have observed will occur.
One workaround is to temporarily disable the converter in recursive calls using a thread static Boolean. A thread static is used because, in some situations including asp.net-web-api, instances of JSON converters will be shared between threads. In such situations disabling the converter via an instance property will not be thread-safe.
public class FJson : JsonConverter
{
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t;
using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite))
{
t = JToken.FromObject(value, serializer);
}
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}
JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}
private void WriteJson(JsonWriter writer, JObject value)
{
foreach (var p in value.Properties())
{
if (p.Value is JObject)
WriteJson(writer, (JObject)p.Value);
else
p.WriteTo(writer);
}
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return true; // works for any type
}
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
#region IDisposable Members
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
#endregion
}
Having done this, you can restore the [JsonConverter(typeof(FJson))] to your class A:
[JsonConverter(typeof(FJson))]
public class A
{
}
Demo fiddle #1 here.
A second, simpler workaround for generating a default JToken representation for a type with a JsonConverter applied takes advantage fact that a converter applied to a member supersedes converters applied to the type, or in settings. From the docs:
The priority of which JsonConverter is used is the JsonConverter defined by attribute on a member, then the JsonConverter defined by an attribute on a class, and finally any converters passed to the JsonSerializer.
Thus it is possible to generate a default serialization for your type by nesting it inside a DTO with a single member whose value is an instance of your type and has a dummy converter applied which does nothing but fall back to to default serialization for both reading and writing.
The following extension method and converter do the job:
public static partial class JsonExtensions
{
public static JToken DefaultFromObject(this JsonSerializer serializer, object value)
{
if (value == null)
return JValue.CreateNull();
var dto = Activator.CreateInstance(typeof(DefaultSerializationDTO<>).MakeGenericType(value.GetType()), value);
var root = JObject.FromObject(dto, serializer);
return root["Value"].RemoveFromLowestPossibleParent() ?? JValue.CreateNull();
}
public static object DefaultToObject(this JToken token, Type type, JsonSerializer serializer = null)
{
var oldParent = token.Parent;
var dtoToken = new JObject(new JProperty("Value", token));
var dtoType = typeof(DefaultSerializationDTO<>).MakeGenericType(type);
var dto = (IHasValue)(serializer ?? JsonSerializer.CreateDefault()).Deserialize(dtoToken.CreateReader(), dtoType);
if (oldParent == null)
token.RemoveFromLowestPossibleParent();
return dto == null ? null : dto.GetValue();
}
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
// If the parent is a JProperty, remove that instead of the token itself.
var contained = node.Parent is JProperty ? node.Parent : node;
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (contained is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
interface IHasValue
{
object GetValue();
}
[JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy), IsReference = false)]
class DefaultSerializationDTO<T> : IHasValue
{
public DefaultSerializationDTO(T value) { this.Value = value; }
public DefaultSerializationDTO() { }
[JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)]
public T Value { get; set; }
object IHasValue.GetValue() { return Value; }
}
}
public class NoConverter : JsonConverter
{
// NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
// To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
// By https://stackoverflow.com/users/3744182/dbc
public override bool CanConvert(Type objectType) { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }
public override bool CanRead { get { return false; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); }
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
}
And then use it in FJson.WriteJson() as follows:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken t = serializer.DefaultFromObject(value);
// Remainder as before
if (t.Type != JTokenType.Object)
{
t.WriteTo(writer);
return;
}
JObject o = (JObject)t;
writer.WriteStartObject();
WriteJson(writer, o);
writer.WriteEndObject();
}
The advantages and disadvantages of this approach are that:
It doesn't rely on recursively disabling the converter, and so works correctly with recursive data models.
It doesn't require re-implementing the entire logic of serializing an object from its properties.
It serializes to and deserializes from an intermediate JToken representation. It is not appropriate for use when attempt to stream a default serialization directly to and from a the incoming JsonReader or JsonWriter.
Demo fiddle #2 here.
Notes
Both converter versions only handle writing; reading is not implemented.
To solve the equivalent problem during deserialization, see e.g. Json.NET custom serialization with JsonConverter - how to get the "default" behavior.
Your converter as written creates JSON with duplicated names:
{
"id": 1,
"name": null,
"name": "value"
}
This, while not strictly illegal, is generally considered to be bad practice and so should probably be avoided.
I didn't like the solution posted above so I worked out how the serializer actually serialized the object and tried to distill it down to the minimum:
public override void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
{
JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract( value.GetType() );
writer.WriteStartObject();
foreach ( var property in contract.Properties )
{
writer.WritePropertyName( property.PropertyName );
writer.WriteValue( property.ValueProvider.GetValue(value));
}
writer.WriteEndObject();
}
No stack overflow problem and no need for a recursive disable flag.
I can't comment yet, so sorry for that...but I just wanted to add something to the solution provided by Paul Kiar. His solution really helped me out.
The code of Paul is short and simply works without any custom building of objects.
The only addition I would like to make is to insert a check if the property is ignored. If it is set to be ignored then skip the write for that property:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());
writer.WriteStartObject();
foreach (var property in contract.Properties)
{
if (property.Ignored)
continue;
writer.WritePropertyName(property.PropertyName);
writer.WriteValue(property.ValueProvider.GetValue(value));
}
writer.WriteEndObject();
}
By placing the attribute on class A, it is being called recursively. The first line in WriteJson override is again calling the serializer on class A.
JToken t = JToken.FromObject(value);
This causes a recursive call and hence the StackOverflowException.
From your code, I think you are trying to flatten the heirarchy. You can probably achieve this by putting the converter attribute on the property B, which will avoid the recursion.
//remove the converter from here
public class A
{
public A()
{
this.b = new B();
}
public int id { get; set; }
public string name { get; set; }
[JsonConverter(typeof(FJson))]
public B b { get; set; }
}
Warning: The Json you get here will have two keys called "name" one from class A and the other from class B.

Categories