问题
When I deseiralize the JSON to the C# object below, either using Newtonsoft explicitly or via the model binding mechanism of ASP.NET Web Api, the string id
value is automatically converted to int. I would expect it to throw an exception or raise an error as there is a type mismatch. Is this how JSON is supposed to work in the specs? If not, how can I prevent such an automatic conversion?
JSON: {"id":"4", "name":"a"}
C# model: int id; string name
回答1:
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.
回答2:
What you describe is a feature, since most folks want this kind of behavior. I haven't check, but I bet it uses something like Convert.ChangeType(strValue, propertyType);
that tries to automatically convert from string to target's property type.
If you need it just as string, use Maksim's solution.
Your model can also incorporate an extra property to have both types, if needed:
public class Model
{
public int id { get; set; }
public string idStr => id.ToString();
public string name { get; set; }
}
回答3:
Try just making your C# model a string if that is what you eventually want.
来源:https://stackoverflow.com/questions/41783225/json-deserialization-string-is-automatically-converted-to-int