19

Is it possible to customize the way types are serialized to the response in ASP.NET Core MVC?

In my particular use case I've got a struct, AccountId, that simply wraps around a Guid:

public readonly struct AccountId
{
    public Guid Value { get; }

    // ... 
}

When I return it from an action method, unsurprisingly, it serializes to the following:

{ "value": "F6556C1D-1E8A-4D25-AB06-E8E244067D04" }

Instead, I'd like to automatically unwrap the Value so it serializes to a plain string:

"F6556C1D-1E8A-4D25-AB06-E8E244067D04"

Can MVC be configured to achieve this?

Marc LaFleur
  • 31,987
  • 4
  • 37
  • 63
Michał Dudak
  • 4,923
  • 2
  • 21
  • 28
  • 3
    I think your best bet is a custom `JsonConverter`. JSON.NET is used as the default JSON serializer, so consult their docs. – Chris Pratt Mar 08 '18 at 18:02
  • start from reading about [custom-formatters](https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-formatters) – Set Mar 08 '18 at 18:49
  • @ChrisPratt that seems to be the way to go. I thought there is something more generic (if I ever wanted to return XML instead of JSON, I'd have to configure another formatter), but it'll suffice. – Michał Dudak Mar 10 '18 at 13:57
  • You can try overriding `ToString` on your struct. That'll effect more than just serialization, though. – Chris Pratt Mar 10 '18 at 14:49
  • I've tried it already, but it seems that JSON.NET ignores it. I'll go with custom `JsonConverter` for now and perhaps suggest a more generic solution on MVC issue tracker. – Michał Dudak Mar 10 '18 at 14:59

1 Answers1

36

You can customize the output produced by JSON.NET with a custom converter.

In your case, it would look like this:

[JsonConverter(typeof(AccountIdConverter))]
public readonly struct AccountId
{
    public Guid Value { get; }

    // ... 
}

public class AccountIdConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
        => objectType == typeof(AccountId);

    // this converter is only used for serialization, not to deserialize
    public override bool CanRead => false;

    // implement this if you need to read the string representation to create an AccountId
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        => throw new NotImplementedException();

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (!(value is AccountId accountId))
            throw new JsonSerializationException("Expected AccountId object value.");

        // custom response 
        writer.WriteValue(accountId.Value);
    }
}

If you prefer not to use the JsonConverter attribute, it's possible to add converters in ConfigureServices (requires Microsoft.AspNetCore.Mvc.Formatters.Json):

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc()
        .AddJsonOptions(options => {
            options.SerializerSettings.Converters.Add(new AccountIdConverter());
        });
}
Métoule
  • 13,062
  • 2
  • 56
  • 84
  • 4
    That's almost how I ended up doing this. I just don't like the `JsonConverter` attribute so I added my converter to `SerializerSettings.Converters` in `ConfigureServices`' `AddMvc` – Michał Dudak Mar 10 '18 at 15:51
  • 3
    I've added your alternative to my answer, for completeness. – Métoule Mar 12 '18 at 08:08
  • 1
    This also works with `AddControllersWithViews`, which is what's used in the React.js template with Visual Studio 2019. services.AddControllersWithViews() .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new MyAwesomeConverter()); }); – Becca Dee Sep 05 '19 at 13:43