C# Newtonsoft.Json Custom Deserializer - c#

I'm working with an API that is returning results to me in a different way than I'm used to dealing with, and seemingly non-standard.
For example, here's a snippet of Customer data:
{
"CustomerID": {
"value": "EXAMPLE"
},
"CustomerCurrencyID": {
"value": "USD"
}
}
That "value" property seems very unnecessary, so I would like to see if I can just bypass that all together and deserialize that JSON into an object like so:
class Customer {
public string CustomerID { get; set; }
public string CustomerCurrencyID { get; set; }
}
I'm currently working on writing a custom JsonConverter to handle this, so if I'm heading down the right path just let me know, but any tips/tricks here would be much appreciated!

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.

Related

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.

How to move JSON object's depth one level up in JSON.net using C#?

I am trying to write a converter for a class using JSON.net
When I serialize the object of type JsonObject, I get the following output
{"DataObject":{"id":"1","name":"data name"}}
How can I move the DataObject one level up to get the following output:
{"id":"1","name":"data name"}
You can find the relevant code below.
My class has the following format:
public class JsonObject
{
public JsonObject(IDataObject dataObject)
{
this.DataObject= dataObject;
}
[JsonConverter(typeof(JsonDataObjectConverter))]
public IDataObject DataObject;
}
The DataObject has some properties:
public class MyDataObject : IDataObject
{
[JsonProperty(PropertyName = "id", Required = Required.Always)]
public string Id { get; set; }
[JsonProperty(PropertyName = "name", Required = Required.Always)]
public string Name { get; set; }
}
I referred this page from the documentation and wrote the following converter:
public class JsonDataObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IDataObject).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
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)
{
if (value is MyDataObject dataObject)
{
writer.WriteStartObject();
writer.WritePropertyName("id");
writer.WriteValue(dataObject.Id);
writer.WritePropertyName("name");
writer.WriteValue(dataObject.Name);
writer.WriteEndObject();
}
}
}
Any help is appreciated. Thank you.
I was able to get the desired output by moving the converter to the class instead of the property and ignoring the property using [JsonIgnore]. The property needs to be ignored since the converter for the class will generate the JSON for the property as shown below.
So the JsonObject class will be:
[JsonConverter(typeof(JsonObjectConverter))]
public class JsonObject
{
public JsonObject(IDataObject dataObject)
{
this.DataObject= dataObject;
}
[JsonIgnore]
public IDataObject DataObject;
}
Then create the converter like this:
public class JsonObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IDataObject).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
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)
{
JsonObject jsonObject = (JsonObject)value;
if (jsonObject.DataObject.GetType() == typeof(MyDataObject))
{
MyDataObject dataObject = (MyDataObject) jsonObject.DataObject;
writer.WriteStartObject();
writer.WritePropertyName("id");
writer.WriteValue(dataObject.Id);
writer.WritePropertyName("name");
writer.WriteValue(dataObject.Name);
writer.WriteEndObject();
}
}
}
This gives the desired output of:
{"id":"1","name":"data name"}

Convert string property value to base64 on serialization

Does anyone know if its possible to convert some of the values in a class to Base64 when you serialize the object? I need a way to mark a property to indicate that it needs to be exported as Base64. For example:
using Newtonsoft.Json;
public class MyFoo {
public string Value1 { get; set; }
[ExportThisValueAsBase64]
public string Value2 { get; set; }
}
public void WriteJSON(MyFoo myFoo) {
var contentsToWriteToFile = SerializeObject(myFoo, Formatting.Indented);
}
The expected output would then be:
{ "Value1": "A String", "Value2": base64encodedvalue }
I would also need a way to read the values back in from base64 to the string property in the class.
What I did in the end was, as advised in the comments, to create a JsonConverter
internal class CustomBase64Converter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return System.Text.Encoding.UTF8.GetString((Convert.FromBase64String((string)reader.Value)));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes((string)value)));
}
}
Now on any of my properties I can just add the heading
[JsonConverter(typeof(CustomBase64Converter))]

Reading huge integers with Json.NET

I've got some json with huge integers, in the order of a few hundred digits. I'd like to parse those as BouncyCastle's BigInteger (https://github.com/onovotny/BouncyCastle-PCL/blob/pcl/crypto/src/math/BigInteger.cs).
{
"bigNumber":12093812947635091350945141034598534526723049126743245...
}
So I've implemented a converter, using a contract resolver in the default settings.
internal class BigIntegerConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken jToken = JToken.Load(reader);
return new BigInteger(jToken.ToString());
}
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(BigInteger));
}
}
public class BigIntegerContractResolver : DefaultContractResolver
{
private static readonly JsonConverter bigIntegerConverter = new BigIntegerConverter();
private static Type type = typeof(BigInteger);
protected override JsonConverter ResolveContractConverter(Type objectType)
{
if (objectType == type)
{
return bigIntegerConverter;
}
return base.ResolveContractConverter(objectType);
}
}
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
ContractResolver = new BigIntegerContractResolver()
};
The writer works as it should, writing a (large) integer value instead of the class BigInteger with all its properties etc. However, the reading fails. Neither ReadJson nor CanConvert appear to be invoked.
I get the following exception:
JsonReaderException: JSON integer 340597435091750914358634185762341897561435984635897436598435643875643189576413589743659817456... is too large or small for an Int64.
How do I get Json.NET to parse this number as a string instead of an integer?
Ideally I don't want to have to parse the json string myself first, to add quotes.
If your large number isn't quoted, Json.Net will deserialize it as a System.Numerics.BigInteger. This happens inside the JsonTextReader, well before the converter gets a chance to handle it. So if you want your result type to be Org.BouncyCastle.Math.BigInteger, you'll need to convert from System.Numerics.BigInteger. (Seems a little backwards, I know. The other alternative is to create your own JsonTextReader, but that is probably going to be more trouble than it is worth -- most of the useful bits of the existing reader are in private or internal methods, so subclassing it is not practical.)
I was able to get this converter to work:
class BigIntegerConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Org.BouncyCastle.Math.BigInteger));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
System.Numerics.BigInteger big = (System.Numerics.BigInteger)reader.Value;
return new Org.BouncyCastle.Math.BigInteger(big.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(value.ToString());
}
}
Here is the test program I used. Note that I did not use a resolver. JsonSerializerSettings has a Converters collection, so I just added the BigIntegerConverter to that.
class Program
{
static void Main(string[] args)
{
string json = #"
{
""bigNumber"": 12093812947635091350945141034598534526723049126743245
}";
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new BigIntegerConverter() }
};
Foo foo = JsonConvert.DeserializeObject<Foo>(json);
Console.WriteLine(foo.BigNumber.ToString());
}
}
class Foo
{
public Org.BouncyCastle.Math.BigInteger BigNumber { get; set; }
}
Output:
12093812947635091350945141034598534526723049126743245
You can try creating object that handles the output of the json like this:
public class YourModel
{
[JsonConverter(typeof(CustomConverter<BigInteger>))]
public BigInteger YourProperty{ get; set; }
}
And now it can become more generic for every type you need:
public class CustomConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
your code ..
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//explicitly specify the concrete type we want to create
return serializer.Deserialize<T>(reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
your code ...
}
}

Serialize root-level type differently than lower-level type

Is it possible in Json.NET to serialize a root-level type in the serialization hierarchy differently than a reference encountered at a lower level in the hierarchy?
For example, with this type
class Serialized {
public Serialized Serialized;
public int A;
}
in this setup
var serialized = new Serialized() { A = 1 };
var serialized2 = new Serialized() { A = 2 };
serialized.Serialized = serialized2;
string json = GetJson(serialized);
where json is
{
"A":1
"Serialized": {
"ref":2
}
}
Specifically, the root-level serialization should use the default serialization strategy and the lower-level ones should use a custom converter (or similar).
You need to define custom JsonConverter and decorate the Serialized property with it:
public class PropertyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
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)
{
if (value == null) return;
var serialized = (Serialized)value;
writer.WriteStartObject();
writer.WritePropertyName("ref");
writer.WriteValue(serialized.A);
writer.WriteEndObject();
}
}
And your class:
class Serialized {
[JsonConverter(typeof(PropertyConverter))
public Serialized Serialized;
public int A;
}
This will work only for one level of nesting. If you need it to work of multiple nested levels, you need to modify the converter:
public class PropertyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
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)
{
if (value == null) return;
var serialized = (Serialized)value;
writer.WriteStartObject();
writer.WritePropertyName("ref");
writer.WriteValue(serialized.A);
if(serialized.Serialized != null)
{
writer.WritePropertyName("nested");
writer.WriteValue(JsonConvert.SerializeObject(serialized.Serialized, new JsonConverter[] {new PropertyConverter()}));
}
writer.WriteEndObject();
}
}

Categories

Resources