Using .Net Core 3's new System.Text.Json JsonSerializer, how do you automatically cast types (e.g. int to string and string to int)? For example, this throws an exception because id
in JSON is numeric while C#'s Product.Id
is expecting a string:
public class HomeController : Controller
{
public IActionResult Index()
{
var json = @"{""id"":1,""name"":""Foo""}";
var o = JsonSerializer.Deserialize<Product>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
});
return View();
}
}
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
}
Newtonsoft's Json.Net handled this beautifully. It didn't matter if you were passing in a numeric value while C# was expecting a string (or vice versa), everything got deserialized as expected. How do you handle this using System.Text.Json if you have no control over the type format being passed in as JSON?
A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.
Deserializes the specified JSON string into an Apex object of the specified type. deserializeUntyped(jsonString) Deserializes the specified JSON string into collections of primitive data types. serialize(objectToSerialize) Serializes Apex objects into JSON content.
The JsonSerializer is a static class in the System. Text. Json namespace. It provides functionality for serializing objects to a JSON string and deserializing from a JSON string to objects. The JsonSerializer has the serialize() method with multiple overloads, which is a highly performant method for serialization in .
JavaScript Object Notation (JSON) is a standard text-based format for representing structured data based on JavaScript object syntax. It is commonly used for transmitting data in web applications (e.g., sending some data from the server to the client, so it can be displayed on a web page, or vice versa).
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 a JsonConverter
api which allows us to convert the type as we like.
For example, we can create a generic number
to string
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
In the options, set the NumberHandling property to AllowReadingFromString
:
var o = JsonSerializer.Deserialize<Product>(json, new JsonSerializerOptions
{
// [...]
NumberHandling = JsonNumberHandling.AllowReadingFromString
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With