Selectively use default JSON converter - c#

I use the following in my Web API project's Startup.cs to JSON-serialize Enums into strings:
// Configure JSON Serialization
var jsonSerializationSettings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
jsonSerializationSettings.Formatting = Newtonsoft.Json.Formatting.None;
jsonSerializationSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
This is to avoid decorating every Enum property with [JsonConverter(typeof(StringEnumConverter))]
Now, how can I selectively opt out of my global serialization setting for some Enum properties and use the default serializer that converts to integers?

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.

Related

How to Deserialize Json with Composite Pattern - C#, Json

Since I had to search forever for an answer, that would just work as is, I will provide an example here, when you serialize and deserialize an object with a composite pattern structure.
My problem was deserializing this kind of class structure (https://en.wikipedia.org/wiki/Composite_pattern):
abstract class BaseClass
{
public int Id { get; set; }
}
class Leaf : BaseClass
{
public string Foo { get; set; }
}
class Composite : BaseClass
{
public List<BaseClass> ClassList = new List<BaseClass>();
}
Serialization worked by using:
var composite = new Composite();
JsonConvert.SerializeObject(composite, Formatting.Indented);
Deserialziation didn't work out of box.
Solution for deserialization is to build a CustomConverter:
public class BaseClassJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseClass) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
try
{
var jObject = JObject.Load(reader);
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.Converters.Add(new BaseClassJsonConverter());
if (jObject.ContainsKey("Foo"))
return JsonConvert.DeserializeObject<Leaf>(jObject.ToString(), jsonSerializerSettings);
else if (jObject.ContainsKey("ClassList"))
return JsonConvert.DeserializeObject<Composite>(jObject.ToString(), jsonSerializerSettings);
else
throw new System.SystemException("Class not implemented");
return null;
}
catch (JsonReaderException)
{
return null;
}
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Key difference to other answers I found is that they were missing to put the settings in the ReadJson into the SeserialzieObject again, that way it couldn't handle the deserialization of a recursive structure (Deserializing JSON to abstract class).
Now you either decorate your BaseClass with: [JsonConverter(typeof(BaseConverter))]
Or you call the deserialization with the converter in the settings:
var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.Converters.Add(new BaseClassJsonConverter());
JsonConvert.DeserializeObject<BaseClass>(jsonString, jsonSerializerSettings);

Access already deserialized properties from JsonConverter.ReadJson

I am trying to solve backward compartibility when deserializing old json. Previously there was double property and now it's changed to a custom type.
My idea is to read double and simply convert it using custom json converter.
Before was:
public class A
{
[JsonProperty)]
string Name { get; set; }
[JsonProperty)]
double Value { get; set; }
}
Serialized as
{"Name":"test","Value":33.0}
New one:
public class A
{
[JsonProperty]
[JsonConverter(typeof(MyJsonConverter))]
public MyType Value { get; set; }
}
Serialized as
{"Value":{"Value":33.0,"Name":"test", ...}},
Converter:
public class MyJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => true;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value is double value)
return new MyType(value, ???); // here is the problem, I need Name value here
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
JToken.FromObject(value).WriteTo(writer);
}
but to construct MyType I need string parameter which is a value of another property Name
How to access Name value from converter for Value? How to access anything what has been deserialized? Is there some kind of tree? Tokens tree?
Another thing: in WriteJson method I want to do "nothing" special, is my implementation correct? Or is there an easy way to prevent converter doing anything "special" upon serialization?
You would need to apply a converter to your parent A class:
[JsonConverter(typeof(MyJsonConverter))]
public class A
{
public MyType Value { get; set; }
}
public class AConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(A);
public override bool CanWrite => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
// Check if the keys contains "Name"
string name = jObject["Name"]?.ToString();
var a = new A();
if (name != null)
{
a.Value = new MyType
{
Name = name,
Value = jObject["Value"].Value<double>()
};
}
else
{
a.Value = jObject["Value"].ToObject<MyType>();
}
return a;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
To use default serailisation, just override CanWrite with false.

Convert int to string JsonConvert [duplicate]

I am using JsonConvert.SerializeObject to serialize a model object.
The server expects all fields as strings. My model object has numeric properties and string properties. I can not add attributes to the model object. Is there a way to serialize all property values as if they were strings? I have to support only serialization, not deserialization.
You can provide your own JsonConverter even for numeric types. I've just tried this and it works - it's quick and dirty, and you almost certainly want to extend it to support other numeric types (long, float, double, decimal etc) but it should get you going:
using System;
using System.Globalization;
using Newtonsoft.Json;
public class Model
{
public int Count { get; set; }
public string Text { get; set; }
}
internal sealed class FormatNumbersAsTextConverter : JsonConverter
{
public override bool CanRead => false;
public override bool CanWrite => true;
public override bool CanConvert(Type type) => type == typeof(int);
public override void WriteJson(
JsonWriter writer, object value, JsonSerializer serializer)
{
int number = (int) value;
writer.WriteValue(number.ToString(CultureInfo.InvariantCulture));
}
public override object ReadJson(
JsonReader reader, Type type, object existingValue, JsonSerializer serializer)
{
throw new NotSupportedException();
}
}
class Program
{
static void Main(string[] args)
{
var model = new Model { Count = 10, Text = "hello" };
var settings = new JsonSerializerSettings
{
Converters = { new FormatNumbersAsTextConverter() }
};
Console.WriteLine(JsonConvert.SerializeObject(model, settings));
}
}

Override StringEnumConverter in JSON.net [duplicate]

I use the following in my Web API project's Startup.cs to JSON-serialize Enums into strings:
// Configure JSON Serialization
var jsonSerializationSettings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
jsonSerializationSettings.Formatting = Newtonsoft.Json.Formatting.None;
jsonSerializationSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
This is to avoid decorating every Enum property with [JsonConverter(typeof(StringEnumConverter))]
Now, how can I selectively opt out of my global serialization setting for some Enum properties and use the default serializer that converts to integers?
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.

Using custom JsonConverter and TypeNameHandling in Json.net

I have a class with an interface-typed property like:
public class Foo
{
public IBar Bar { get; set; }
}
I also have multiple concrete implementations of the IBar interface that can be set at runtime. Some of these concrete classes require a custom JsonConverter for serialization & deserialization.
Utilizing the TypeNameHandling.Auto option the non-convertor requiring IBar classes can be serialized and deserialized perfectly. The custom-serialized classes on the other hand have no $type name output and while they are serialized as expected, they cannot be deserialized to their concrete type.
I attempted to write-out the $type name metadata myself within the custom JsonConverter; however, on deserialization the converter is then being bypassed entirely.
Is there a workaround or proper way of handling such a situation?
I solved the similar problem and I found a solution. It's not very elegant and I think there should be a better way, but at least it works. So my idea was to have JsonConverter per each type that implements IBar and one converter for IBar itself.
So let's start from models:
public interface IBar { }
public class BarA : IBar { }
public class Foo
{
public IBar Bar { get; set; }
}
Now let's create converter for IBar. It will be used only when deserializing JSON. It will try to read $type variable and call converter for implementing type:
public class BarConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObj = JObject.Load(reader);
var type = jObj.Value<string>("$type");
if (type == GetTypeString<BarA>())
{
return new BarAJsonConverter().ReadJson(reader, objectType, jObj, serializer);
}
// Other implementations if IBar
throw new NotSupportedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof (IBar);
}
public override bool CanWrite
{
get { return false; }
}
private string GetTypeString<T>()
{
var typeOfT = typeof (T);
return string.Format("{0}, {1}", typeOfT.FullName, typeOfT.Assembly.GetName().Name);
}
}
And this is converter for BarA class:
public class BarAJsonConverter : BarBaseJsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// '$type' property will be added because used serializer has TypeNameHandling = TypeNameHandling.Objects
GetSerializer().Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var existingJObj = existingValue as JObject;
if (existingJObj != null)
{
return existingJObj.ToObject<BarA>(GetSerializer());
}
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(BarA);
}
}
You may notice that it's inherited from BarBaseJsonConverter class, not JsonConverter. And also we do not use serializer parameter in WriteJson and ReadJson methods. There is a problem with using serializer parameter inside custom converters. You can read more here. We need to create new instance of JsonSerializer and base class is a good candidate for that:
public abstract class BarBaseJsonConverter : JsonConverter
{
public JsonSerializer GetSerializer()
{
var serializerSettings = JsonHelper.DefaultSerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.Objects;
var converters = serializerSettings.Converters != null
? serializerSettings.Converters.ToList()
: new List<JsonConverter>();
var thisConverter = converters.FirstOrDefault(x => x.GetType() == GetType());
if (thisConverter != null)
{
converters.Remove(thisConverter);
}
serializerSettings.Converters = converters;
return JsonSerializer.Create(serializerSettings);
}
}
JsonHelper is just a class to create JsonSerializerSettings:
public static class JsonHelper
{
public static JsonSerializerSettings DefaultSerializerSettings
{
get
{
return new JsonSerializerSettings
{
Converters = new JsonConverter[] { new BarConverter(), new BarAJsonConverter() }
};
}
}
}
Now it will work and you still can use your custom converters for both serialization and deserialization:
var obj = new Foo { Bar = new BarA() };
var json = JsonConvert.SerializeObject(obj, JsonHelper.DefaultSerializerSettings);
var dObj = JsonConvert.DeserializeObject<Foo>(json, JsonHelper.DefaultSerializerSettings);
Using information from Alesandr Ivanov's answer above, I created a generic WrappedJsonConverter<T> class that wraps (and unwraps) concrete classes requiring a converter using a $wrappedType metadata property that follows the same type name serialization as the standard $type.
The WrappedJsonConverter<T> is added as a converter to the Interface (ie. IBar), but otherwise this wrapper is completely transparent to classes that do not require a converter and also requires no changes to the wrapped converters.
I used a slightly different hack to get around the converter/serializer looping (static fields), but it does not require any knowledge of the serializer settings being used, and allows for the IBar object graph to have child IBar properties.
For wrapped objects the Json looks like:
"IBarProperty" : {
"$wrappedType" : "Namespace.ConcreteBar, Namespace",
"$wrappedValue" : {
"ConvertedID" : 90,
"ConvertedPropID" : 70
...
}
}
The full gist can be found here.
public class WrappedJsonConverter<T> : JsonConverter<T> where T : class
{
[ThreadStatic]
private static bool _canWrite = true;
[ThreadStatic]
private static bool _canRead = true;
public override bool CanWrite
{
get
{
if (_canWrite)
return true;
_canWrite = true;
return false;
}
}
public override bool CanRead
{
get
{
if (_canRead)
return true;
_canRead = true;
return false;
}
}
public override T ReadJson(JsonReader reader, T existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
JToken token;
T value;
if (!jsonObject.TryGetValue("$wrappedType", out token))
{
//The static _canRead is a terrible hack to get around the serialization loop...
_canRead = false;
value = jsonObject.ToObject<T>(serializer);
_canRead = true;
return value;
}
var typeName = jsonObject.GetValue("$wrappedType").Value<string>();
var type = JsonExtensions.GetTypeFromJsonTypeName(typeName, serializer.Binder);
var converter = serializer.Converters.FirstOrDefault(c => c.CanConvert(type) && c.CanRead);
var wrappedObjectReader = jsonObject.GetValue("$wrappedValue").CreateReader();
wrappedObjectReader.Read();
if (converter == null)
{
_canRead = false;
value = (T)serializer.Deserialize(wrappedObjectReader, type);
_canRead = true;
}
else
{
value = (T)converter.ReadJson(wrappedObjectReader, type, existingValue, serializer);
}
return value;
}
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
{
var type = value.GetType();
var converter = serializer.Converters.FirstOrDefault(c => c.CanConvert(type) && c.CanWrite);
if (converter == null)
{
//This is a terrible hack to get around the serialization loop...
_canWrite = false;
serializer.Serialize(writer, value, type);
_canWrite = true;
return;
}
writer.WriteStartObject();
{
writer.WritePropertyName("$wrappedType");
writer.WriteValue(type.GetJsonSimpleTypeName());
writer.WritePropertyName("$wrappedValue");
converter.WriteJson(writer, value, serializer);
}
writer.WriteEndObject();
}
}

Categories

Resources