Selectively Use Default JSON Converter

Selectively use default JSON converter

You could add a dummy converter to the properties in question that does nothing:

public class NoConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// Note - not called when attached directly via [JsonConverter(typeof(NoConverter))]
throw new NotImplementedException();
}

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();
}
}

Then attach it to the property using [JsonConverter(typeof(NoConverter))]. Having done so, the JsonConverter attribute's converter supersedes the globally specified converter, but since CanRead and CanWrite both return false no conversion is performed. For collections of enums, you could use [JsonProperty(ItemConverterType = typeof(NoConverter))].

For instance, if you define the types:

public enum Foo { A, B, C }

public class RootObject
{
[JsonConverter(typeof(NoConverter))]
public Foo FooAsInteger { get; set; }

public Foo FooAsString { get; set; }
}

Then

var root = new RootObject { FooAsInteger = Foo.B, FooAsString = Foo.B };

var json = JsonConvert.SerializeObject(root, Formatting.Indented, new StringEnumConverter());

Console.WriteLine(json);

Produces the output

{
"FooAsInteger": 1,
"FooAsString": "B"
}

Note that you can also apply NoConverter directly to the enum, if you want all occurrences of the enum in all data models to be serialized as an integer:

[JsonConverter(typeof(NoConverter))]
public enum FooAlwaysAsInteger { A, B, C }

Sample fiddle.

C# JsonConvert using the default converter instead of the custom converter

The order in which JsonConverters are selected is documented as follows:

The priority of which JsonConverter is used is member attribute, then class attribute, and finally any converters passed to the JsonSerializer.

Thus you cannot disable a JsonConverter applied via JsonConverterAttribute using JsonSerializerSettings.Converters. Instead, you have the following options.

Firstly, if your TheShape is being referred to directly by some type you control, you could grab NoConverter from this answer to Selectively use default JSON converter and apply it to the referring members using JsonConverterAttribute or JsonPropertyAttribute.ItemConverterType, e.g. as follows:

public class ShapeContainer
{
[JsonConverter(typeof(NoConverter))]
public TheShape Shape { get; set; }

[JsonProperty(ItemConverterType = typeof(NoConverter))]
public List<TheShape> Shapes { get; set; }
}

Now NoConverter will supersede TheShapeSerializer for the properties where it is applied, and cause Json.NET to fall back on default serialization.

Secondly, if you cannot add member attributes to types where TheShape is used, you could create a custom contract resolver that overrides DefaultContractResolver.ResolveContractConverter and returns null for TheShape. First define the following contract resolver:

public class ConverterDisablingContractResolver : DefaultContractResolver
{
readonly HashSet<Type> types;

public ConverterDisablingContractResolver(IEnumerable<Type> types)
{
if (types == null)
throw new ArgumentNullException();
this.types = new HashSet<Type>(types);
}

bool ContainsType(Type type)
{
return types.Contains(type);
}

protected override JsonConverter ResolveContractConverter(Type objectType)
{
// This could be enhanced to deal with inheritance. I.e. if TBase is in types and has a converter then
// its converter should not be used for TDerived -- but if TDerived has its own converter then it should still be
// used, so simply returning null for TDerived would be wrong.
if (types.Contains(objectType))
return null;
return base.ResolveContractConverter(objectType);
}
}

Then, define a static member somewhere as follows, for performance reasons described here:

static IContractResolver shapeResolver = new ConverterDisablingContractResolver(new[] { typeof(TheShape) });

And serialize as follows:

var settings = new JsonSerializerSettings
{
ContractResolver = shapeResolver,
};
var json = JsonConvert.SerializeObject(root, settings);

Demo fiddle showing both options here.

Along the same lines, is there a way to have multiple converters that can be selected at serialization time based on a given condition?

Obviously you could add different converters to JsonSerializerSettings.Converters depending on some runtime condition. But if you want to supersede statically applied converters with runtime converters, you would need to set up your types appropriately, e.g. by using OverridableJsonConverterDecorator from this answer to Why Json.net does not use customized IsoDateTimeConverter?.

C# Newtonsoft.Json Custom Deserializer

You can do this with a generic custom JsonConverter such as the following:

public class WrapWithValueConverter<TValue> : JsonConverter
{
// Here we take advantage of the fact that a converter applied to a property has highest precedence to avoid an infinite recursion.
class DTO { [JsonConverter(typeof(NoConverter))] public TValue value { get; set; } public object GetValue() => value; }

public override bool CanConvert(Type objectType) => typeof(TValue).IsAssignableFrom(objectType);

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> serializer.Serialize(writer, new DTO { value = (TValue)value });

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> serializer.Deserialize<DTO>(reader)?.GetValue();
}

public class NoConverter : JsonConverter
{
// NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182
// By https://stackoverflow.com/users/3744182/dbc
// To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter
public override bool CanConvert(Type objectType) { throw new NotImplementedException(); /* This converter should only be applied via attributes */ }
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

Then you can apply it to your model as follows:

class Customer {
[JsonConverter(typeof(WrapWithValueConverter<string>))]
public string CustomerID { get; set; }
[JsonConverter(typeof(WrapWithValueConverter<string>))]
public string CustomerCurrencyID { get; set; }
}

Demo fiddle #1 here.

Or, if you want all strings to be wrapped in a {"value": <string value>} object, you can add the converter to JsonSerializerSettings.Converters when serializing and deserializing:

var settings = new JsonSerializerSettings
{
Converters = { new WrapWithValueConverter<string>() },
};

var model = JsonConvert.DeserializeObject<Customer>(json, settings);

var json2 = JsonConvert.SerializeObject(model, Formatting.Indented, settings);

Demo fiddle #2 here.

If your value is an enum and you want to serialize it as a string, you can replace NoConverter with StringEnumConverter by using the following:

public class WrapEnumWithValueConverter<TEnum> : JsonConverter where TEnum: Enum
{
// Here we take advantage of the fact that a converter applied to a property has highest precedence to avoid an infinite recursion.
class DTO { [JsonConverter(typeof(StringEnumConverter))] public TEnum value { get; set; } public object GetValue() => value; }

public override bool CanConvert(Type objectType) => typeof(TEnum).IsAssignableFrom(objectType);

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> serializer.Serialize(writer, new DTO { value = (TEnum)value });

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> serializer.Deserialize<DTO>(reader)?.GetValue();
}

Demo fiddle #3 here.

How can I choose what type to deserialize at runtime based on the structure of the json?

You have a couple of problems here:

  1. You are getting an infinite recursion in calls to ReadJson() because your converter is registered with the serializer you are using to do the nested deserialization, either through settings or by directly applying [JsonConverter(typeof(DifficultyConverter))] to Difficulty.

    The standard solution to avoid this is to manually allocate your Difficulty and then use serializer.Populate() to deserialize its members (e.g. as shown in this answer to Json.NET custom serialization with JsonConverter - how to get the "default" behavior) -- but you are also using PreserveReferencesHandling.Objects, which does not work with this approach.

    What does work with reference preservation is to adopt the approach from this answer to JSON.Net throws StackOverflowException when using [JsonConvert()] and deserialize to some DTO that contains a property of type Difficulty which has a superseding converter applied directly to the property.

  2. serializer.Deserialize<JValue>(reader); may advance the reader past the current token. This will cause the later attempt to deserialize as an object to fail.

    Instead, just check the JsonReader.TokenType or preload into a JToken and check the Type.

Putting the above together, your converter should look like the following:

public class DifficultyConverter : JsonConverter
{
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)
{
var token = JToken.Load(reader);

switch (token.Type)
{
case JTokenType.Null:
return null;

case JTokenType.Integer:
{
var intv = (int)token;
return Library.EnumerateDifficulties().FirstOrDefault(d => d.CombatModifier == intv);
}

case JTokenType.Object:
return token.DefaultToObject(objectType, serializer);

default:
throw new JsonSerializationException(string.Format("Unknown token {0}", token.Type));
}
}

public override bool CanWrite => false;

public override bool CanConvert(Type objectType) => objectType == typeof(Difficulty);
}

Using the following extension methods:

public static partial class JsonExtensions
{
public static object DefaultToObject(this JToken token, Type type, JsonSerializer serializer = null)
{
var oldParent = token.Parent;

var dtoToken = new JObject(new JProperty(nameof(DefaultSerializationDTO<object>.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(Newtonsoft.Json.Serialization.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; }

public object GetValue() => 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(); }
}

Demo fiddle here.

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

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:

  1. It doesn't rely on recursively disabling the converter, and so works correctly with recursive data models.

  2. It doesn't require re-implementing the entire logic of serializing an object from its properties.

  3. 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.



Related Topics



Leave a reply



Submit