JSON Deserialization – String Is Automatically Converted To Int

This is a feature of Json.NET: when deserializing a primitive type, it will convert the primitive JSON value to the target c# type whenever possible. Since the string "4" can be converted to an integer, deserialization succeeds. If you don’t want this feature, you can create a custom JsonConverter for integral types that checks that the token being read is really numeric (or null, for a nullable value):

public class StrictIntConverter : JsonConverter
{
    readonly JsonSerializer defaultSerializer = new JsonSerializer();

    public override bool CanConvert(Type objectType) 
    {
        return objectType.IsIntegerType();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        switch (reader.TokenType)
        {
            case JsonToken.Integer:
            case JsonToken.Float: // Accepts numbers like 4.00
            case JsonToken.Null:
                return defaultSerializer.Deserialize(reader, objectType);
            default:
                throw new JsonSerializationException(string.Format("Token \"{0}\" of type {1} was not a JSON integer", reader.Value, reader.TokenType));
        }
    }

    public override bool CanWrite { get { return false; } }

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

public static class JsonExtensions
{
    public static bool IsIntegerType(this Type type)
    {
        type = Nullable.GetUnderlyingType(type) ?? type;
        if (type == typeof(long)
            || type == typeof(ulong)
            || type == typeof(int)
            || type == typeof(uint)
            || type == typeof(short)
            || type == typeof(ushort)
            || type == typeof(byte)
            || type == typeof(sbyte)
            || type == typeof(System.Numerics.BigInteger))
            return true;
        return false;
    }        
}

Note the converter accepts values like 4.00 as integers. You can change this by removing the check for JsonToken.Float if it does not meet your needs.

You can apply it to your model directly as follows:

public class RootObject
{
    [JsonConverter(typeof(StrictIntConverter))]
    public int id { get; set; }

    public string name { get; set; }
}

Or include the converter in JsonSerializerSettings to apply it to all integral fields:

var settings = new JsonSerializerSettings
{
    Converters = { new StrictIntConverter() },
};
var root = JsonConvert.DeserializeObject<RootObject>(json, settings);

Finally, to apply JSON serializer settings globally in Web API, see for instance here.

Leave a Comment