Reputation: 1931
Simply, I have a bare bones ASP.NET Core 3.1 MVC application that has a model with a custom JsonConverter attribute on one of the properties. If a view has @Json.Serialize with just the property as an input, the custom JsonConverter doesn't get called.
My Views/Home/Index.cshtml view:
@model JsonSerializer.MyModel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JsonSerializer</title>
</head>
<body>
<div>
Number is @Json.Serialize(Model.Number)
</div>
<div>
ConvertedNumber is @Json.Serialize(Model.ConvertedNumber)
</div>
</body>
</html>
My controller, model & custom Json converter:
public class HomeController : Controller
{
public IActionResult Index()
{
var model = new MyModel
{
Number = 1.234567,
ConvertedNumber = 1.234567
};
return View("~/Views/Home/Index.cshtml", model);
}
}
public class MyModel
{
public double Number { get; set; }
[JsonConverter(typeof(NumberConverter))]
public double ConvertedNumber { get; set; }
}
public class NumberConverter : JsonConverter<double>
{
public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
{
writer.WriteStringValue($"{value:F2}");
}
public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
This renders:
Number is 1.234567
ConvertedNumber is 1.234567
But if I add the NumberConverter to the global JSON options, it will work, but this will apply the customer converter to every attribute that has the double type:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews().AddJsonOptions(c=>
{
c.JsonSerializerOptions.Converters.Add(new NumberConverter());
});
}
This renders:
Number is "1.23"
ConvertedNumber is "1.23"
Isn't this a bit inconsistent or am I missing something? Either both the attribute and the global setting should call the custom converter, or neither should. I've checked and it is the same in .NET Core 2.2 which uses Newtonsoft Json to serialize.
You might ask what's the point serializing a single double property into JSON, but sometimes the property could be a more complex type like a list or another object for which you want custom JSON output for.
Upvotes: 2
Views: 2482
Reputation: 25360
Isn't this a bit inconsistent or am I missing something?
Actually that's an expected behavior of System.Text.JSON
API. As you know, ASP.NET Core uses the new System.Text.Json by default as of 3.0. The new JsonConverter
of System.Text.JSON
differs quite a lot from Newtonsoft.JSON
. As for this scenario, there's official docs covering this. See Converter registration precedence:
During serialization or deserialization, a converter is chosen for each JSON element in the following order, listed from highest priority to lowest:
- [JsonConverter] applied to a property.
- A converter added to the Converters collection.
- [JsonConverter] applied to a custom value type or POCO. If multiple custom converters for a type are registered in the Converters collection, the first converter that returns true for CanConvert is used.
A built-in converter is chosen only if no applicable custom converter is registered.)
Your converter won't be invoked because you're actually passing a @Model.ConvertedNumber
property value (which is a double value) instead of @Model
:
<div>
ConvertedNumber is @Json.Serialize(Model.ConvertedNumber)
</div>
The expression @Model.ConvertedNumber
is an expression that is evaluated to a double value, while @Model
is an expression that is evaluated into a MyModel
instance.
Your above invocation actually calls Json.Serialize(a_double_value)
behind the scenes. According to above priority, the whole process is :
@Json.Serialize(Model.ConvertedNumber)
. As it is a function invocation, it must know the argument firstly.Model.ConvertedNumber
and then get a double valuedouble
result into the Json.Serialize(a_double_value)
double
type has no children property to be serialized, the rule 1 won't apply at all.double
type has no [JsonConverter]
attribute, the rule 3 doesn't applyJsonConverterAttribute
in System.Text.Json
?According to above analysis, if you don't want to add it into the global converter collections, either pass a Model like
@Json.Serialize(Model) // honor the converter because System.Text.Json knows that property has a `[JsonConverterAttribute]`
or pass an extra options manually:
@{
var jsonSerializerOptions =new JsonSerializerOptions();
jsonSerializerOptions.Converters.Add(a_dobule_converter);
}
@JsonSerializer.Serialize(Model.Date, jsonSerializerOptions)
Upvotes: 4