Reputation: 673
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());
}
}
Upvotes: 3
Views: 3566
Reputation: 116795
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.
Upvotes: 3