I am trying to convert this Newtonsoft.Json.JsonConverter to System.Text.Json. However, I was only able to use a single primitive type, say double and even there I cant apply the converter on nullable (double?). How can I convert this to support nullable and all number formats (float, double).
Newtonsoft.Json
public class DecimalRoundingJsonConverter : JsonConverter
{
private readonly int _numberOfDecimals;
public DecimalRoundingJsonConverter() : this(6)
{
}
public DecimalRoundingJsonConverter(int numberOfDecimals)
{
_numberOfDecimals = numberOfDecimals;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
double input = 0;
if (value is decimal)
{
var d = (decimal)value;
input = Convert.ToDouble(d);
}
else if (value is float)
{
var d = (float)value;
input = Convert.ToDouble(d);
}
else
{
input = (double)value;
}
var rounded = Math.Round(input, _numberOfDecimals);
writer.WriteValue((decimal)rounded);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanRead
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(decimal);
}
}
System.Text.Json (basic)
public class DecimalRoundingJsonConverter : JsonConverter<double>
{
private readonly int _numberOfDecimals = 6;
public override double Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(
Utf8JsonWriter writer,
double dvalue,
JsonSerializerOptions options)
{
double input = (double)dvalue;
var rounded = Math.Round(input, _numberOfDecimals);
writer.WriteStringValue(rounded.ToString());
}
}
You can create a converter that applies to all float, double and decimal values, as well as nullables of the same, by creating a JsonConverter<object> and overriding JsonConverter<object>.CanConvert(Type) to return true only for the six relevant types.
The following does the job:
public class RoundingJsonConverter : RoundingJsonConverterBase
{
// The converter works for float, double & decimal. Max number of decimals for double is 15, for decimal is 28, so throw an exception of numberOfDecimals > 28.
public RoundingJsonConverter(int numberOfDecimals) => NumberOfDecimals = (numberOfDecimals < 0 || numberOfDecimals > 28 ? throw new ArgumentOutOfRangeException(nameof(numberOfDecimals)) : numberOfDecimals);
protected override int NumberOfDecimals { get; }
}
public class RoundingTo2DigitsJsonConverter : RoundingJsonConverterBase
{
protected override int NumberOfDecimals { get; } = 2;
}
public class RoundingTo6DigitsJsonConverter : RoundingJsonConverterBase
{
protected override int NumberOfDecimals { get; } = 6;
}
public abstract class RoundingJsonConverterBase : JsonConverter<object>
{
protected abstract int NumberOfDecimals { get; }
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
typeToConvert = Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert;
if (typeToConvert == typeof(decimal))
return reader.GetDecimal();
else if (typeToConvert == typeof(double))
return reader.GetDouble();
else if (typeToConvert == typeof(float))
return (float)reader.GetDouble();
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
switch (value)
{
case double d:
writer.WriteNumberValue(Math.Round(d, Math.Min(15, NumberOfDecimals)));
break;
case decimal d:
writer.WriteNumberValue(Math.Round(d, NumberOfDecimals));
break;
case float f:
writer.WriteNumberValue((decimal)Math.Round((decimal)f, NumberOfDecimals));
break;
default:
throw new NotImplementedException();
}
}
public override bool CanConvert(Type typeToConvert)
{
typeToConvert = Nullable.GetUnderlyingType(typeToConvert) ?? typeToConvert;
return typeToConvert == typeof(double) || typeToConvert == typeof(decimal) || typeToConvert == typeof(float);
}
}
Notes:
System.Text.Json has no equivalent to Newtonsoft's JsonConverter.CanRead so you must implement Read() as well as Write().
When adding the converter to JsonSerializerOptions.Converters use DecimalRoundingJsonConverter and pass the required number of digits as a constructor argument, e.g.:
var options = new JsonSerializerOptions
{
Converters = { new RoundingJsonConverter(6) },
};
However, if you are applying the converter via attributes, Microsoft does not allow passing of converter parameters (see here for confirmation) so you will need to create a specific converter type for each required number of digits, e.g.
public class RoundingTo2DigitsJsonConverter : RoundingJsonConverterBase
{
protected override int NumberOfDecimals { get; } = 2;
}
public class RoundingTo6DigitsJsonConverter : RoundingJsonConverterBase
{
protected override int NumberOfDecimals { get; } = 6;
}
And then apply e.g. as follows:
[JsonConverter(typeof(RoundingTo6DigitsJsonConverter))]
public decimal? SixDecimalPlaceValue { get; set; }
Nullable.GetUnderlyingType(Type) can be use used to get the underlying type of a nullable type such as decimal? or double?.
JsonConverter<T>.Write() is never called for null values of nullables unless JsonConverter<T>.HandleNull is overridden to return true.
Demo fiddle here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With