10

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 ?

Oyvind
  • 568
  • 2
  • 6
  • 22
  • 2
    Looks like this is not available out of the box. [JsonConverterAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconverterattribute?view=net-5.0) has nothing similar to ConverterParameters. The [migration guide](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to) doesn't even mention compile-time converter attributes. You may need to subclass your `MyDecimalConverter` as needed, say `MyDecimalConverter3Digit`. – dbc Feb 01 '21 at 17:23
  • I can imagine workarounds if you were applying the converter to the type rather than to the property, but I don't see any way for the converter to know the property to which it was applied. – dbc Feb 01 '21 at 17:24
  • @PavelAnikhouski yeah, did you ? – Oyvind Feb 01 '21 at 21:54
  • 1
    @dbc yep, that's my conclusion too so far. A little less elegant though.. – Oyvind Feb 01 '21 at 21:55

2 Answers2

9

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"
}
bbalazs
  • 91
  • 1
  • 3
5

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());
        }); 

After that you could use it in the model, like this:

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

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.

Zhi Lv
  • 18,845
  • 1
  • 19
  • 30
  • This is the obvious way of doing it (and what I ended up doing). It does not answer the question though, and making concrete classes for each number of decimals is not very elegant in my opinion. Also, Newtownsoft is deprecated, as System.Text.Json [is the new way](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-5-0) of doing this. Configuring the application with legacy libraries is very likely to cause problems later on. – Oyvind Feb 02 '21 at 07:37
  • Hi @Oyvind, Yes, you are right. But, at present the `System.Text.Json` doesn't allow to add parameters, you could consider using above method as a temporary workaround, and you can also consider [filing an issue](https://github.com/dotnet/runtime/issues/new) to find out if support for your scenario can be added. – Zhi Lv Feb 02 '21 at 07:57
  • *After that you could use it in the model...* You don't need to register the converter with `Startup.ConfigureServices` in order to apply it to the model. Converters applied directly to properties take precedence as explained in [Converter registration precedence](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0#converter-registration-precedence). – dbc Feb 02 '21 at 15:45