Innofactor / EfCoreJsonValueConverter

JSON ValueConverter for EF Core 3.0+
GNU Lesser General Public License v3.0
96 stars 17 forks source link

Customise JSON serialisation #4

Open cocowalla opened 5 years ago

cocowalla commented 5 years ago

It's possible to customise JSON serialisation at a global level by changing JsonConvert.DefaultSettings, but it would be good to have the option to customise it just for EF Core JSON serialisation by specifying a JsonSerializerSettings, for example to prevent cyclic references from being serialised, configure null handling, or to add type information to the serialised JSON to support interfaces:


var jsonSettings = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
     ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    TypeNameHandling = TypeNameHandling.Objects,
    TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple
};

var json = JsonConvert.SerializeObject(obj, jsonSettings);
var obj = JsonConvert.DeserializeObject<T>>(json, jsonSettings);

At it's most basic, this could be implemented as a global setting by adding a static JsonSerializerSettings in this library (or a configuration class that composes it). I imagine a global setting would be what most people would want.

Alternatively (or additionally), the HasJsonValueConversion extension method could have an optional JsonSerializerSettings argument added to it. Not sure if there would really be a need to configure it for each property though.

ghost commented 5 years ago

I'd like this feature too. I've implemented the most basic solution in a fork, adding a static JsonSerializerSettings exposed from JsonHelper and making sure both the converter and comparer use the settings.

In my application, I added an extension method for DbContextOptionsBuilder to modify the settings in my Startup.cs without referring to the JsonHelper directly.

public static class DbContextOptionsBuilderExtensions
{
    public static DbContextOptionsBuilder AddJsonConverterSettings(this DbContextOptionsBuilder dbContextOptions, Action<JsonSerializerSettings> setupAction)
    {
        setupAction(JsonHelper.SerializerSettings);

        return dbContextOptions;
    }
}

And use it like so:

services.AddDbContext<ApplicationDbContext>(options =>
{
    options.UseSqlServer(Configuration.GetConnectionString("Application"));

    options.AddJsonConverterSettings(settings =>
    {
        settings.ContractResolver = new DefaultContractResolver
        {
            NamingStrategy = new CamelCaseNamingStrategy()
        };
    });
});

I suppose that extension method could also be included in the library instead and then JsonHelper could remain internal.

AlexEngblom commented 5 years ago

@cocowalla this seems useful suggestion. I suppose that global settings should be sufficient for usual needs and pretty straightforward to implement.

@webwizgordon this looks viable to me, as well as the consideration to add required extensions in library for less wiring. I wonder if it would be better to pass configuration settings inside the ApplicationDbContext setup though. For example if the context implementation is shared between multiple applications.

ghost commented 5 years ago

You might also want to consider having a separate instance of JsonSerializerSettings per DbContext type instead of a single static instance shared between all DbContext types. Someone could conceivably have multiple types of DbContexts and may want different JsonSerializerSettings for each for some reason.

lvmajor commented 4 years ago

Is there any update about this? That would be really useful !

JasonLandbridge commented 3 years ago

For those looking for a workaround, add this extension method to your project:

    /// <summary>
    /// Extensions for <see cref="T:Microsoft.EntityFrameworkCore.Metadata.Builders.PropertyBuilder" />.
    /// </summary>
    public static class PropertyBuilderExtensions
    {
        /// <summary>Serializes field as JSON blob in database.</summary>
        public static PropertyBuilder<T> HasJsonValueConversion<T>(
            this PropertyBuilder<T> propertyBuilder)
            where T : class
        {
            propertyBuilder.HasConversion(
                v => JsonConvert.SerializeObject(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore}),
                v => JsonConvert.DeserializeObject<T>(v, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore }));

            return propertyBuilder;
        }
    }

Here you can change your serializerSettings and then use it per property like this:

builder
    .Property(x => x.MetaData)
    .HasJsonValueConversion();

This method also allows you to change on a per-context basis, just make a second extension method with your unique serializer settings, or tweak the HasJsonValueConversion() method to pass a new JsonSerializerSettings().