4

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

toebens
  • 4,039
  • 6
  • 24
  • 31
  • Which line causes an exception? Please, share a stack trace and `DecimalDbValue` class. Also, which .NET version are you using? – Pavel Anikhouski Jan 07 '20 at 09:24
  • 1
    Answer in this thread should help you to resolve the issue [Force decimal type in class definition during serialization](https://stackoverflow.com/questions/48179763/force-decimal-type-in-class-definition-during-serialization) – Pavel Anikhouski Jan 07 '20 at 09:31
  • @PavelAnikhouski Convert.ToDecimal(reader.Value); within ReadJson is causing the exception. We are using .net 4.7.2. I cant post the whole struct it is verly lengthy. The exception is thrown on `Convert.ToDecimal(reader.Value)` within `ReadJson` When trying the other solution `serializer.Populate(reader, existingValue);` throws `Newtonsoft.Json.JsonSerializationException : Unexpected initial token 'Float' when populating object. Expected JSON object or array. Path '', line 1, position 31.` – toebens Jan 07 '20 at 10:03
  • Please, add this information to your question. I can' reproduce your issue without `DecimalDbValue` properties, `IsNull` and `Value` at least – Pavel Anikhouski Jan 07 '20 at 10:07
  • i've added a stripped down version of the struct – toebens Jan 07 '20 at 11:07
  • https://github.com/JamesNK/Newtonsoft.Json/issues/1904 – Hans Passant Jan 14 '20 at 09:58
  • yes that's the exception. however as i have a custom struct and i only want it being serialized/deserialized as a decimal i don't know what to do to solve the issue with my custom JsonConverter – toebens Jan 14 '20 at 14:36
  • As explained in [Force decimal type in class definition during serialization](https://stackoverflow.com/a/48180588/3744182), you need to toggle the `JsonTextReader` to decimal mode before beginning to deserialize the `DecimalDbValue`. That can be done by adding `[JsonConverter(typeof(FloatParseHandlingConverter), FloatParseHandling.Decimal)]` to the parent object. Does that answer your question? – dbc Jan 14 '20 at 22:19
  • No this isnt working. BTW it is only the ctor parameter. I tested the way before. See above comments – toebens Jan 15 '20 at 02:54
  • 1
    @toebens - if `DecimalDbValue` is the **root object** then `FloatParseHandlingConverter` won't work because it needs to be applied to the container type. Applying it to `DecimalDbValue` itself won't work because the decimal value will already have been read in as a `double` by the time `ReadJson()` is called. In such a situation, you need to set `JsonSerializerSettings.FloatParseHandling` explicitly. But when not the root object, applying `FloatParseHandlingConverter` to the container will work. See https://dotnetfiddle.net/BmgKYj. Do you ever actually use `DecimalDbValue` as your root? – dbc Jan 15 '20 at 19:06
  • GitHub [issue](https://github.com/JamesNK/Newtonsoft.Json/issues/1726) with the similar problem – Pavel Anikhouski Jan 16 '20 at 19:51

2 Answers2

1

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
}
toebens
  • 4,039
  • 6
  • 24
  • 31
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

fibriZo raZiel
  • 894
  • 11
  • 10