Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I restore the int deserialization behavior after upgrading Json.NET?

Tags:

c#

json.net

I have some .NET code that deserializes JSON objects created by a webservice running a dynamic language. Because the source is dynamic, it sometimes serializes integral values in float format (e. g. 2 gets serialized to "2.0").

With Json.NET 4.0.4, this worked seamlessly (seems like rounding was applied when deserializing). With the upgrade to Json.NET 4.5, though, deserializing 2.0 now throws a FormatException. Here's the code:

// works as expected in both versions
var s = "2";
Console.WriteLine(JsonConvert.DeserializeObject<int>(s));

// throws FormatException in 4.5 only
var s = "2.0";
Console.WriteLine(JsonConvert.DeserializeObject<int>(s));

// throws FormatException in 4.5, rounds to 3 in 4.0.4
var s = "2.6";
Console.WriteLine(JsonConvert.DeserializeObject<int>(s));

Is there any easy way to restore the original behavior? The ideal behavior would be to deserialize only numbers with integral values, but in any format (e. g. 2.0, 1e10, but not 2.5), but I'd settle for the 4.0.4 behavior.

like image 755
ChaseMedallion Avatar asked Jul 19 '13 12:07

ChaseMedallion


People also ask

What is Deserializing a JSON?

The process whereby a lower-level format (e.g. that has been transferred over a network, or stored in a data store) is translated into a readable object or other data structure. In JavaScript, for example, you can deserialize a JSON string to an object by calling the function JSON.

What is serialization and deserialization in JSON?

JSON is a format that encodes objects in a string. Serialization means to convert an object into that string, and deserialization is its inverse operation (convert string -> object).


1 Answers

You can do this by making a custom JsonConverter which will handle rounding (or discarding) the decimal values. It might look something like this:

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JValue jsonValue = serializer.Deserialize<JValue>(reader);

        if (jsonValue.Type == JTokenType.Float)
        {
            return (int)Math.Round(jsonValue.Value<double>());
        }
        else if (jsonValue.Type == JTokenType.Integer)
        {
            return jsonValue.Value<int>();
        }

        throw new FormatException();
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

You can then use the custom converter like this:

JsonSerializerSettings settings = new JsonSerializerSettings 
{
    Converters = new List<JsonConverter> { new CustomIntConverter() } 
};

string json = @"[2.6, 0, 4.1, 5, -3, -2.2]";

List<int> list = JsonConvert.DeserializeObject<List<int>>(json, settings);

foreach (int val in list)
{
    Console.WriteLine(val);
}

The output of the above would be this:

3
0
4
5
-3
-2

If you would rather the converter ignore decimal values rather than round them, replace the following line of code

        return (int)Math.Round(jsonValue.Value<double>());

with this:

        return (existingValue ?? default(int));

After making that change, the output of the test code above would then look like this:

0
0
0
5
-3
0
like image 141
Brian Rogers Avatar answered Oct 03 '22 01:10

Brian Rogers