Oyvind
Oyvind

Reputation: 597

Custom JsonConverter with parameters in .NET Core

I wrote my own JsonConverter (using ASP.Net Core 3.1), which I intended to use on my DTOs.

In previous versions (Newtonsoft.Json), you could add a constructor on the custom JsonConverter class, and specify parameters using JsonConverterAttribute:

[JsonConverter(typeof(MyDecimalConverter), 3)]
public decimal MyProp { get; set; }

However, after the migration to System.Text.Json.Serialization, this option is not there anymore. The new JsonConverterAttribute simply does not have a constructor for it.

What is the new way of achieving this ?

Upvotes: 12

Views: 21175

Answers (2)

Zhi Lv
Zhi Lv

Reputation: 21383

After comparing the JsonConverterAttribute definition in Newtonsoft.Json and System.Text.Json.Serialization, we can find that: when using the System.Text.Json.Serialization, it doesn't allow to enter the converter parameters.

enter image description here

So, as dbc said, you could create a Custom JsonConverter to convert the decimal with 3 digits, like this:

public class MyDecimalConverter3Digit : JsonConverter<decimal>
{
    public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
    public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(Decimal.Round(value, 3).ToString());
    }
}

Then, register the JsonConverter in Startup.ConfigureServices method:

        services.AddControllersWithViews().AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.Converters.Add(new MyDecimalConverter3Digit());
        }); 

Alternatively, you can use it directly in the model via the JsonConverterAttribute, like this:

public class Calculate
{
    public decimal Price { get; set; }
    [JsonConverter(typeof(MyDecimalConverter3Digit))]
    public decimal Rate { get; set; }
}

(This takes precedence over any converters registered in the options.)

Besides, you could also configure your application to use the Newtonsoft.Json serialize and deserialize json. Please refer the following steps:

  1. Install the Microsoft.AspNetCore.Mvc.NewtonsoftJson package via NuGet or use the following command:

     Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson
    
  2. Add .AddNewtonsoftJson() method at the end of the AddControllersWithViews(), like this:

     services.AddControllers().AddNewtonsoftJson();
     services.AddControllersWithViews().AddNewtonsoftJson();
     services.AddRazorPages().AddNewtonsoftJson(); 
    

    When you create custom converter, remember to use the Newtonsoft reference, instead of System.Text.Json.Serialization. Like this:

    enter image description here

    After that, you could use the custom converter with parameters.

Upvotes: 5

bbalazs
bbalazs

Reputation: 131

I also missed this feature from System.Text.Json.Serialization, and used to use a custom JsonConverter for every formatting case, but I really did not like. My best workorund to solve this a cleaner way - at least for my taste - uses a custom JsonConverterAttribute. I use this in .NET6 apps, but according to the docs, it works with the Core 3.1, too.

So the example: Create a Converter that requires a constructor parameter (based on the question that is done already). In my case it is the format string.

public class DoubleConverter : JsonConverter<double>
{
    private readonly string _format;

    public DoubleConverter(string format) => _format = format;


    public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // Not needed for the example.
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString(_format));
    }
}

Then create a custom JsonAttribute. This part makes the usage easy, because it accepts the needed constructor parameter, and creates the cutom JsonConverter using that parameter.

[AttributeUsage(AttributeTargets.Property)]
public class DoubleSerializationStringFormatAttribute : JsonConverterAttribute
{
    private readonly string _format;

    public DoubleSerializationStringFormatAttribute(string format) => _format = format;

    public override JsonConverter CreateConverter(Type typeToConvert)
    {
        if (typeToConvert != typeof(double))
        {
            throw new ArgumentException(
                $"This converter only works with double, and it was provided {typeToConvert.Name}.");
        }

        return new DoubleConverter(_format);
    }
}

Now the attribute can be used on any property:

public class DataClass
{
    [DoubleSerializationStringFormat("N2")]
    public double Prop1 { get; set; }

    [DoubleSerializationStringFormat("N5")]
    public double Prop2 { get; set; }
}

Finally I can serialize the DataClass instance:

var data = new DataClass() {Prop1 = 10.5678, Prop2 = 3.14159267};
var serialized = JsonSerializer.Serialize(data);
Console.Write(serialized);

And I get the numbers serialized according to the specified format:

{
    "Prop1":"10.57",
    "Prop2":"3.14159"
}

Upvotes: 13

Related Questions