3

I have a class ChargesDetail and am trying to deserialize json as shown below. Here I have used datatype that is Amount.

public class ChargesDetail
{
   public double DiscountRate { get; set; }
   public Amount DiscountAmount { get; set; }
}

public class Amount:IConvertible 
{
    private double _val = 0;
    private int _decimal = 5;

    public Amount()
    {
    }

    public Amount(double amount): this()            
    {
       // this.Value = amount;
        _val = Math.Round(amount, _decimal);
    }

    #region IConvertible Members

    // Implementation snipped

    #endregion
}

And my JSON looks like:

{ "DiscountRate":0.0, "DiscountAmount":0.0 }

Am trying to deserialize as like this:

T result = JsonConvert.DeserializeObject<ChargesDetail>(json);

It is giving me an exception like:

Invalid cast from 'System.Double' to 'Amount'.

at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider) at System.Double.System.IConvertible.ToType(Type type, IFormatProvider provider) at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureType(JsonReader reader, Object value, CultureInfo culture, JsonContract contract, Type targetType)

How to deserialize json to this customized datatype?

  • I cannot remove IConvertible, bcz it throws System.StackOverflowException in some calculations.
  • I cannot change Amount type to double type, bcz there are more than 100 properties of same type and doing calculations in 'Amount' class.
dbc
  • 104,963
  • 20
  • 228
  • 340
Anitha B.M
  • 37
  • 5

4 Answers4

5

As your question indicates you are using , you can serialize your Amount as a single decimal value using a custom JsonConverter:

public class AmountConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Amount);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Would decimal be more appropriate than double?
        var value = serializer.Deserialize<double?>(reader);
        if (value == null)
            return null;
        return new Amount(value.Value);

    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteValue(((Amount)value).Value);
    }
}

You can then either apply it to your model as follows:

[JsonConverter(typeof(AmountConverter))]
public class Amount : IConvertible
{
    private double _val = 0;
    private int _decimal = 5;

    public double Value { get { return _val; } }

    public Amount()
    {

    }

    public Amount(double amount)
        : this()
    {
        // this.Value = amount;
        _val = Math.Round(amount, _decimal);
    }

    #region IConvertible Members
    #endregion
}

Or add it in JsonSerializerSettings.Converters when serializing and deserializing:

var settings = new JsonSerializerSettings
{
    Converters = { new AmountConverter() },
};
var = JsonConvert.DeserializeObject<T>(json, settings);

Notes:

  1. If Amount is intended to represent a monetary quantity you might consider switching to decimal from double.

    But if you do, you will need to switch to FloatParseHandling.Decimal at some higher level to prevent loss of precision during parsing by JsonTextReader. This can be done e.g. by setting JsonSerializerSettings.FloatParseHandling globally or by grabbing FloatParseHandlingConverter from this answer to Force decimal type in class definition during serialization and applying it to the parent class ChargesDetail like so:

    [JsonConverter(typeof(FloatParseHandlingConverter), FloatParseHandling.Decimal)]
    public class ChargesDetail
    {
        public decimal DiscountRate { get; set; }
        public Amount DiscountAmount { get; set; }
    }
    

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

Your class Amount does not Implement the Interface IConvertable.

BigDream
  • 58
  • 6
  • I have implemented. public class Amount:IConvertible { private double _val = 0; private int _decimal = 5; public Amount() { } public Amount(double amount): this() { // this.Value = amount; _val = Math.Round(amount, _decimal); } } – Anitha B.M Dec 13 '18 at 06:51
0

While going through your JSON.

{ "FileVersion":"11.03", "ChargesDetail":{ "DiscountRate":0.0, "DiscountAmount":0.0 } } 

public class ChargesDetail
{
   public double DiscountRate { get; set; }
   public Amount DiscountAmount { get; set; }
}

public class Amount:IConvertible 
{ 

}

Here you need to change the type of DiscountAmount to double because the Structure you are mapping is not right.

Or if you change the JSON to

{ "FileVersion":"11.03", "ChargesDetail":{ "DiscountRate":0.0, "DiscountAmount":{ "DiscountAmountVar":0.0 }} }

and Class to

public class ChargesDetail
{
   public double DiscountRate { get; set; }
   public Amount DiscountAmount { get; set; }
}

public class Amount:IConvertible 
{ 
   public double DiscountAmountVar{get;set;}
}

Then your previous mapping will work.

  • thank you for answering. But above code does not work for me. I have implemented Amount. My actual code is: public class ChargesDetail { public double DiscountRate { get; set; } public Amount DiscountAmount { get; set; } } public class Amount:IConvertible { private double _val = 0; private int _decimal = 5; public Amount() { } public Amount(double amount): this() { // this.Value = amount; _val = Math.Round(amount, _decimal); } – Anitha B.M Dec 13 '18 at 06:46
  • @AnithaB.M - in that case, please edit your question to include a full [mcve] so we can help you. – dbc Dec 13 '18 at 07:31
  • if I change to double it will work, but there are more than 100 properties in which we are using 'Amount' datatype and there are some methods too for calculation purpose. Changing into 'double' may result wrong calculation( decimal places) – Anitha B.M Dec 13 '18 at 07:33
  • Hello Anitha, Can you please elaborate what exactly you want to do here? – Shuchin Prakash Dec 13 '18 at 08:35
0

On your class Amount you can remove the IConvertible and just implement as :

public class Amount {
    public decimal DiscountAmountVar { get; set; }
}

With this and your other class:

    public class ChargesDetail
{
   public decimal DiscountRate { get; set; }
   public Amount DiscountAmount { get; set; }
}

And this JSON : ****EDIT****

string json =
            @"{""DiscountRate"":12.0, ""DiscountAmount"":{ ""DiscountAmountVar"":13.0 } }";

**EDIT: Just realized that you need to remove one level of the Json.

You should be able to make the JsonConvert.

The main code :

class Program
{
    static void Main(string[] args)
    {
        string json =
            @"{""DiscountRate"":12.0, ""DiscountAmount"":{ ""DiscountAmountVar"":13.0 } }";

        var converted = JsonConvert.DeserializeObject<ChargesDetail>(json);

        Console.WriteLine(converted.DiscountAmount);
    }
}


public class ChargesDetail
{
    public decimal DiscountRate { get; set; }
    public Amount DiscountAmount { get; set; }
}

public class Amount 
{
    public decimal DiscountAmountVar { get; set; }
}
gatsby
  • 1,148
  • 11
  • 12
  • If I remove IConvertible it is throwing exception while assigning data to properties. System.StackOverflowException was unhandled HResult=-2147023895 Message=Exception of type 'System.StackOverflowException' was thrown. – Anitha B.M Dec 13 '18 at 07:19
  • Just paste the new version, hope it helps :) !! – gatsby Dec 13 '18 at 07:32