How can i set FloatParseHandling.Decimal for a custom JsonConverter?
we have a struct DecimalDbValue that internally only holds one decimal field that i want to be de/serialized for all its types.
It uses a magic number (decimal.MinValue) for indicating a "null" value. It was created before .net 2.0 having nullable value types!
This is a spripped down version of our struct::
[Serializable]
[JsonConverter(typeof(DecimalDbValueJsonConverter))]
public struct DecimalDbValue : ISerializable
{
private readonly Decimal _decValue;
public DecimalDbValue(
decimal init)
{
_decValue = init;
}
[JsonConstructor]
public DecimalDbValue(
decimal? init)
{
if (init.HasValue)
_decValue = init.Value;
else
_decValue = decimal.MinValue;
}
private DecimalDbValue(
SerializationInfo objSerializationInfo,
StreamingContext objStreamingContext)
{
_decValue = objSerializationInfo.GetDecimal("value");
}
public bool IsNotNull
{
get
{
return !IsNull;
}
}
public bool IsNull
{
get
{
return _decValue.Equals(Decimal.MinValue);
}
}
public Decimal Value
{
get
{
return _decValue;
}
}
public void GetObjectData(
SerializationInfo info,
StreamingContext context)
{
info.AddValue("value", _decValue);
}
}
I created a JsonConverter:
class DecimalDbValueJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DecimalDbValue) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = reader.Value == null ? (decimal?)null : Convert.ToDecimal(reader.Value);
return new DecimalDbValue(value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var dbValue = (DecimalDbValue)value;
if (dbValue.IsNull)
writer.WriteNull();
else
writer.WriteValue(dbValue.Value);
}
}
and set attribute [JsonConverter(typeof(DecimalDbValueJsonConverter))] on the DecimalDbValue struct
I have added a test:
[Test]
public void TestMaxDecimalDbValue()
{
var s = new DecimalDbValue(decimal.MaxValue);
var json = JsonConvert.SerializeObject(s, Formatting.Indented);
var x = JsonConvert.DeserializeObject<DecimalDbValue>(json);
Assert.AreEqual(s, x);
}
but it throws:
System.OverflowException : Value was either too large or too small for a Decimal.
How can i set FloatParseHandling.Decimal for the JsonConverter? How to make it work for MaxValue as well? Is there any other way?
Actually i would like to have it serialize/deserialized exactly like an decimal? (nullable decimal)
Thanks
It seems to be impossible.
Therefor i removed the JsonConverter completely.
Set this attribute on the struct:
[JsonObject(MemberSerialization.OptIn)]
as well as this on the constructor:
[JsonConstructor]
public DecimalDbValue(
decimal? init) //IMPORTANT: if you change the name of init - rename the JsonProperty below too - you might break all existing json out there thou!
as well as this on the get property:
[JsonProperty("init")]
public Decimal Value
{
get
{
return _decValue;
}
}
Unfortunately this bloats the json:
{
"init": 79228162514264337593543950335.0
}
In case it helps, you can set it for the whole JsonSerializer:
var settings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
FloatParseHandling = FloatParseHandling.Decimal
};
_innerSerializer = Newtonsoft.Json.JsonSerializer.CreateDefault(settings);
You might also want to have a look on FloatFormatHandling to handle your nullables.
https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_FloatFormatHandling.htm
Related
I am using this to deserialize my Json response from an Api.
var apiResponse = await GetAsync<MyResponseModel>(request);
In my response model there is property that is an int, but the api for some reason formats it as a float.
So it looks like this:
{
"Quantity": 6.000
}
Now I parse it with this trick:
[JsonProperty("Quantity")]
private float QuantityFloat {
set => Quantity = IsInt(value) ? (int) value: throw new ArgumentException("Tried to parse number to Quantity that is not an int.");
}
public int Quantity { get; set; }
private static bool IsInt(float value)
{
var x = (int) value;
var temp2 = value - x;
return temp2 <= 0;
}
My linter now complains: "Properties with only setters are confusing and counterintuitive. Instead, a property getter should be added if possible, or the property should be replaced with a setter method."
So I was asking myself if there is a better more elegant way of doing this.
I would suggest creating a JsonConverter<int> that can handle both integer and decimal-formatted values:
(This solution is for System.Text.Json)
public class IntConverter : JsonConverter<int>
{
public override int Read(ref Utf8JsonReader reader, System.Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TryGetInt32(out var intVal))
{
return intVal;
}
// customize this part as necessary to satisfactorily deserialize
// the float value as int, or throw exception, etc.
float floatValue = reader.GetSingle();
return (int)floatValue;
}
public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
Then you can decorate your property with [JsonConverter(typeof(IntConverter))] which will cause the converter to be used during serialization:
public class Model
{
[JsonConverter(typeof(IntConverter))]
public int Quantity { get; set; }
}
Handling it like this takes away the need for any extra properties, thus simplifying your solution. This is especially true if you have to handle this kind of scenario in multiple places.
Try it online
JSON.NET Solution:
public class IntConverter : JsonConverter<int>
{
public override int ReadJson(JsonReader reader, Type objectType, int existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Float)
{
// customize this part as necessary to satisfactorily deserialize
// the float value as int, or throw exception, etc.
double value = (double)reader.Value;
return (int)value;
}
long intVal = (long)reader.Value;
return (int)intVal;
}
public override void WriteJson(JsonWriter writer, int value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
with the following model:
public class Model
{
[JsonConverter(typeof(IntConverter))]
public int Quantity { get; set; }
}
Try it online
Is there any built in settings in json.net to disable the conversion of numbers to booleans. I would prefer it to return an error instead : (without writing a Custom Converter for each class that has a bool property )
public class MyClass
{
public bool flag { get; set; }
}
var str = #"{"flag":123}";
MyClass result = JsonConvert.DeserializeObject<MyClass>(str) ; // flag is true !
You shouldn't use different types in one property to get one bool result.
But if you wish not to declare one custom JsonConvert for every property in the class, you can always create DefaultSettings in your code.
void Initialize()
{
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter>
{
new BoolJsonConverter()
}
};
}
// Create JsonConvert you want to add to DefaultSettings
public class BoolJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(bool);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
}
var json = #"{""flag"":123}"
var myClass = JsonConvert.DeserializeObject<MyClass>(json);
I'm assuming this is a Deserialize only situation. If that is the case, you could have your int value go into an int and use another var to hold the bool. Something like -
public class MyClass
{
public int flagInt { get; set; }
[JsonIgnore]
public bool flag
{
get
{
switch (flagInt)
{
case 0: return false;
case 1: return true;
default: throw new Exception("Cannot convert flag to bool");
}
}
}
}
Note that I don't have a setter on flag so doing something like flag = false will not have expected results and will not serialize correctly either.
I have a client which can call two different versions of a service.
One service only sends a single value:
{
"value" : { ... }
}
The second service always returns multiple values:
{
"values" : [
{ ... },
{ ... }
]
}
Ideally, I'd like to represent this with a single object in my client classes so the user never sees whether it's a single value or multiple values.
public class MyValues
{
public List<Stuff> Values { get; set; }
public Thing Other { get; set; }
}
I think that the only way I'll be able to accomplish this is with a custom JsonConverter class which I apply to MyValues, but I really only want to do something custom when I'm deserializing the property value. I can't seem to figure out if an IContractResolver would be a better way to go (e.g. somehow attach a phantom property to MyValues that deserializes value and puts it into Values.
If I create a custom converter, how to I tell it to deserialize everything else normally (e.g. if Other has an extra properties make sure they are handled appropriately, etc.)
Rather than writing a JsonConverter, you could make a set-only property Value on your MyValues, like so:
public class MyValues
{
[JsonProperty]
Stuff Value
{
set
{
(Values = Values ?? new List<Stuff>(1)).Clear();
Values.Add(value);
}
}
public List<Stuff> Values { get; set; }
public Thing Other { get; set; }
}
It could be public or private if marked with [JsonProperty]. In this case Json.NET will call the Value setter if the singleton "value" property is encountered in the JSON, and call the Values setter if the array "values" property is encountered. Since the property is set-only only the array property will be re-serialized.
To make a custom JsonConverter that has special processing for a few properties of a type but uses default processing for the remainder, you can load the JSON into a JObject, detach and process the custom properties, then populate the remainder from the JObject with JsonSerializer.Populate(), like so:
class MyValuesConverter : CustomPropertyConverterBase<MyValues>
{
protected override void ProcessCustomProperties(JObject obj, MyValues value, JsonSerializer serializer)
{
// Remove the value property for manual deserialization, and deserialize
var jValue = obj.GetValue("value", StringComparison.OrdinalIgnoreCase).RemoveFromLowestPossibleParent();
if (jValue != null)
{
(value.Values = value.Values ?? new List<Stuff>()).Clear();
value.Values.Add(jValue.ToObject<Stuff>(serializer));
}
}
}
public abstract class CustomPropertyConverterBase<T> : JsonConverter where T : class
{
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var jObj = JObject.Load(reader);
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType);
var value = existingValue as T ?? (T)contract.DefaultCreator();
ProcessCustomProperties(jObj, value, serializer);
// Populate the remaining properties.
using (var subReader = jObj.CreateReader())
{
serializer.Populate(subReader, value);
}
return value;
}
protected abstract void ProcessCustomProperties(JObject obj, T value, JsonSerializer serializer);
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
if (contained != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (node.Parent is JProperty)
((JProperty)node.Parent).Value = null;
return node;
}
}
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();
}
}
I am receiving json from a server which I am converting to an object using Json.Net. For one member I am using the StringEnumConverter which works really perfect.
However all of a sudden the server decided to use strings which also can start with a number which results in a JsonSerilizationException - obviously because enums cannot start with a number in .Net.
Now I am trying to find a solution to handle that.My first approach was to add a "_" when Reading the Json (so my enums in the code would have a starting _ when they are followed by a number) and when writing the json I would delete the starting _ (if a number is following). To achieve this I copied the StringEnumConverter into my namespace and tried to change the according part in the WriteJson and ReadJson methods. However I cannot use the StringEnumConverter since there are other dependencies I cannot access in my own namespace.
Is there any elegant solution to this problem?
You can create a JsonConverter and trim the digits from the front
public class Json_34159840
{
public static string JsonStr = #"{""enum"":""1Value"",""name"":""James"",""enum2"":""1""}";
public static void ParseJson()
{
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new EnumConverter() }
};
// Later on...
var result = JsonConvert.DeserializeObject<JsonClass>(JsonStr);
Console.WriteLine(result.Enum);
Console.WriteLine(result.Enum2);
Console.WriteLine(result.Name);
}
}
public class EnumConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var str = value.ToString();
if (Regex.IsMatch(str, #"^_"))
{
writer.WriteValue(str.Substring(1));
}
else
{
writer.WriteValue(str);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = reader.Value.ToString();
if (Regex.IsMatch(value, #"^\d+$"))
{
return Enum.Parse(objectType, value);
}
if (Regex.IsMatch(value, #"^\d+"))
{
value = "_" + value;
}
return Enum.Parse(objectType, value);
}
public override bool CanConvert(Type objectType)
{
//You might want to do a more specific check like
//return objectType == typeof(JsonEnum);
return objectType.IsEnum;
}
}
public enum JsonEnum
{
_0Default,
_1Value
}
public class JsonClass
{
public string Name { get; set; }
public JsonEnum Enum { get; set; }
public JsonEnum Enum2 { get; set; }
}
Hope this helps.
EDIT: Added support for integers :D
A simple strategy is to deserialize the value into a string property and then convert into your own data type (enum or otherwise) via an accessor method or a secondary getter-only property.