System.Text.Json: Deserialize JSON with automatic casting
In the options, set the NumberHandling property to AllowReadingFromString
:
var o = JsonSerializer.Deserialize<Product>(json, new JsonSerializerOptions
{
// [...]
NumberHandling = JsonNumberHandling.AllowReadingFromString
});
Edit: You can use JsonNumberHandlingAttribute
and it handles everything correctly in 1 line, no need to write any code:
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public class HomeController : Controller
....
Original answer:
The new
System.Text.Json
api exposes aJsonConverter
api which allows us to convert the type as we like.For example, we can create a generic
number
tostring
converter:public class AutoNumberToStringConverter : JsonConverter<object> { public override bool CanConvert(Type typeToConvert) { return typeof(string) == typeToConvert; } public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if(reader.TokenType == JsonTokenType.Number) { return reader.TryGetInt64(out long l) ? l.ToString(): reader.GetDouble().ToString(); } if(reader.TokenType == JsonTokenType.String) { return reader.GetString(); } using(JsonDocument document = JsonDocument.ParseValue(ref reader)){ return document.RootElement.Clone().ToString(); } } public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) { writer.WriteStringValue( value.ToString()); } }
When working with MVC/Razor Page, we can register this converter in startup:
services.AddControllersWithViews().AddJsonOptions(opts => { opts.JsonSerializerOptions.PropertyNameCaseInsensitive= true; opts.JsonSerializerOptions.Converters.Insert(0, new AutoNumberToStringConverter()); });
and then the MVC/Razor will handle the type conversion automatically.
Or if you like to control the serialization/deserialization manually:
var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, }; opts.Converters.Add(new AutoNumberToStringConverter()); var o = JsonSerializer.Deserialize<Product>(json,opts) ;
In a similar way you can enable string to number type conversion as below :
public class AutoStringToNumberConverter : JsonConverter<object> { public override bool CanConvert(Type typeToConvert) { // see https://stackoverflow.com/questions/1749966/c-sharp-how-to-determine-whether-a-type-is-a-number switch (Type.GetTypeCode(typeToConvert)) { case TypeCode.Byte: case TypeCode.SByte: case TypeCode.UInt16: case TypeCode.UInt32: case TypeCode.UInt64: case TypeCode.Int16: case TypeCode.Int32: case TypeCode.Int64: case TypeCode.Decimal: case TypeCode.Double: case TypeCode.Single: return true; default: return false; } } public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if(reader.TokenType == JsonTokenType.String) { var s = reader.GetString() ; return int.TryParse(s,out var i) ? i : (double.TryParse(s, out var d) ? d : throw new Exception($"unable to parse {s} to number") ); } if(reader.TokenType == JsonTokenType.Number) { return reader.TryGetInt64(out long l) ? l: reader.GetDouble(); } using(JsonDocument document = JsonDocument.ParseValue(ref reader)){ throw new Exception($"unable to parse {document.RootElement.ToString()} to number"); } } public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) { var str = value.ToString(); // I don't want to write int/decimal/double/... for each case, so I just convert it to string . You might want to replace it with strong type version. if(int.TryParse(str, out var i)){ writer.WriteNumberValue(i); } else if(double.TryParse(str, out var d)){ writer.WriteNumberValue(d); } else{ throw new Exception($"unable to parse {str} to number"); } } }
You can use JsonNumberHandlingAttribute in your model class in order to specify how to treat number deserialization. The allowed options are specified in JsonNumberHandling enum.
Example of usage:
public class Product
{
[JsonNumberHandling(JsonNumberHandling.WriteAsString)]
public string Id { get; set; }
public string Name { get; set; }
}
If serialization from string
to int
is required, you can use JsonNumberHandling.AllowReadingFromString