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?

5
  • 3
    I think your best bet is a custom JsonConverter. JSON.NET is used as the default JSON serializer, so consult their docs. Commented Mar 8, 2018 at 18:02
  • start from reading about custom-formatters Commented Mar 8, 2018 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. Commented Mar 10, 2018 at 13:57
  • You can try overriding ToString on your struct. That'll effect more than just serialization, though. Commented Mar 10, 2018 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. Commented Mar 10, 2018 at 14:59

1 Answer 1

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());
        });
}
Sign up to request clarification or add additional context in comments.

3 Comments

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
I've added your alternative to my answer, for completeness.
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()); });

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.