Skip to content

Commit c13d950

Browse files
author
Bart Koelman
authored
Newtonsoft serializer settings (#720)
* Removed ISerializerOptions * Replaced temporary properties on options with SerializerSettings usage * Replaced usages of JsonConvert.SerializeObject to respect custom serializer settings * Replaced IResourceNameFormatter with options.SerializerSettings to control naming convention * Added explicit attribute name to ensure that value is respected in input/output * Replaced 'omitNull' and 'omitDefault' query string parameters with 'nulls' and 'defaults'. Making them a single word allows us to not take the casing convention into account. Note this inverts the meaning of true/false in the query string value. * Fixed: update deep property on shared settings that were only shallow-copied breaks with concurrent requests * Replaced internal booleans with enums to improve readability. Updated tests to cover all cases. Bugfix where default setting was never used, because the expression default(object) equals null, instead of the default value of the runtime type. * Added test to see serializer override being used in output * Minor serialization fixes * Removed commented-out files
1 parent 2eb0aea commit c13d950

File tree

70 files changed

+1141
-1004
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1141
-1004
lines changed

benchmarks/DependencyFactory.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using JsonApiDotNetCore.Builders;
3+
using JsonApiDotNetCore.Configuration;
34
using JsonApiDotNetCore.Internal.Contracts;
45
using JsonApiDotNetCore.Models;
56
using JsonApiDotNetCore.Query;
@@ -9,9 +10,9 @@ namespace Benchmarks
910
{
1011
internal static class DependencyFactory
1112
{
12-
public static IResourceGraph CreateResourceGraph()
13+
public static IResourceGraph CreateResourceGraph(IJsonApiOptions options)
1314
{
14-
IResourceGraphBuilder builder = new ResourceGraphBuilder();
15+
IResourceGraphBuilder builder = new ResourceGraphBuilder(options);
1516
builder.AddResource<BenchmarkResource>(BenchmarkResourcePublicNames.Type);
1617
return builder.Build();
1718
}

benchmarks/Query/QueryParserBenchmarks.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class QueryParserBenchmarks
2323
public QueryParserBenchmarks()
2424
{
2525
IJsonApiOptions options = new JsonApiOptions();
26-
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph();
26+
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
2727

2828
var currentRequest = new CurrentRequest();
2929
currentRequest.SetRequestResource(resourceGraph.GetResourceContext(typeof(BenchmarkResource)));
@@ -57,13 +57,13 @@ private static QueryParameterParser CreateQueryParameterDiscoveryForAll(IResourc
5757
ISortService sortService = new SortService(resourceDefinitionProvider, resourceGraph, currentRequest);
5858
ISparseFieldsService sparseFieldsService = new SparseFieldsService(resourceGraph, currentRequest);
5959
IPageService pageService = new PageService(options, resourceGraph, currentRequest);
60-
IOmitDefaultService omitDefaultService = new OmitDefaultService(options);
61-
IOmitNullService omitNullService = new OmitNullService(options);
60+
IDefaultsService defaultsService = new DefaultsService(options);
61+
INullsService nullsService = new NullsService(options);
6262

6363
var queryServices = new List<IQueryParameterService>
6464
{
65-
includeService, filterService, sortService, sparseFieldsService, pageService, omitDefaultService,
66-
omitNullService
65+
includeService, filterService, sortService, sparseFieldsService, pageService, defaultsService,
66+
nullsService
6767
};
6868

6969
return new QueryParameterParser(options, queryStringAccessor, queryServices, NullLoggerFactory.Instance);

benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using BenchmarkDotNet.Attributes;
4+
using JsonApiDotNetCore.Configuration;
45
using JsonApiDotNetCore.Internal.Contracts;
56
using JsonApiDotNetCore.Models;
67
using JsonApiDotNetCore.Serialization;
@@ -32,7 +33,8 @@ public class JsonApiDeserializerBenchmarks
3233

3334
public JsonApiDeserializerBenchmarks()
3435
{
35-
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph();
36+
var options = new JsonApiOptions();
37+
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
3638
var targetedFields = new TargetedFields();
3739

3840
_jsonApiDeserializer = new RequestDeserializer(resourceGraph, targetedFields);

benchmarks/Serialization/JsonApiSerializerBenchmarks.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using BenchmarkDotNet.Attributes;
3+
using JsonApiDotNetCore.Configuration;
34
using JsonApiDotNetCore.Graph;
45
using JsonApiDotNetCore.Internal.Contracts;
56
using JsonApiDotNetCore.Managers;
@@ -24,7 +25,8 @@ public class JsonApiSerializerBenchmarks
2425

2526
public JsonApiSerializerBenchmarks()
2627
{
27-
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph();
28+
var options = new JsonApiOptions();
29+
IResourceGraph resourceGraph = DependencyFactory.CreateResourceGraph(options);
2830
IFieldsToSerialize fieldsToSerialize = CreateFieldsToSerialize(resourceGraph);
2931

3032
var metaBuilderMock = new Mock<IMetaBuilder<BenchmarkResource>>();
@@ -34,7 +36,7 @@ public JsonApiSerializerBenchmarks()
3436
var resourceObjectBuilder = new ResourceObjectBuilder(resourceGraph, new ResourceObjectBuilderSettings());
3537

3638
_jsonApiSerializer = new ResponseSerializer<BenchmarkResource>(metaBuilderMock.Object, linkBuilderMock.Object,
37-
includeBuilderMock.Object, fieldsToSerialize, resourceObjectBuilder, new CamelCaseFormatter());
39+
includeBuilderMock.Object, fieldsToSerialize, resourceObjectBuilder, options);
3840
}
3941

4042
private static FieldsToSerialize CreateFieldsToSerialize(IResourceGraph resourceGraph)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace JsonApiDotNetCoreExample.Models
2+
{
3+
public enum Gender
4+
{
5+
Unknown,
6+
Male,
7+
Female
8+
}
9+
}

src/Examples/JsonApiDotNetCoreExample/Models/Person.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ public sealed class Person : Identifiable, IIsLockable
2020
[Attr]
2121
public string LastName { get; set; }
2222

23-
[Attr]
23+
[Attr("the-Age")]
2424
public int Age { get; set; }
2525

26+
[Attr]
27+
public Gender Gender { get; set; }
28+
2629
[HasMany]
2730
public List<TodoItem> TodoItems { get; set; }
2831

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using JsonApiDotNetCore.Configuration;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Newtonsoft.Json.Serialization;
4+
5+
namespace JsonApiDotNetCoreExample
6+
{
7+
/// <summary>
8+
/// This should be in JsonApiDotNetCoreExampleTests project but changes in .net core 3.0
9+
/// do no longer allow that. See https://github.com/aspnet/AspNetCore/issues/15373.
10+
/// </summary>
11+
public sealed class KebabCaseStartup : Startup
12+
{
13+
public KebabCaseStartup(IWebHostEnvironment env) : base(env)
14+
{
15+
}
16+
17+
protected override void ConfigureJsonApiOptions(JsonApiOptions options)
18+
{
19+
base.ConfigureJsonApiOptions(options);
20+
21+
((DefaultContractResolver)options.SerializerSettings.ContractResolver).NamingStrategy = new KebabCaseNamingStrategy();
22+
}
23+
}
24+
}
Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
using Microsoft.AspNetCore.Hosting;
2-
using Microsoft.Extensions.DependencyInjection;
3-
using JsonApiDotNetCoreExample.Data;
4-
using Microsoft.EntityFrameworkCore;
5-
using JsonApiDotNetCore.Extensions;
6-
using System.Reflection;
2+
using JsonApiDotNetCore.Configuration;
73

84
namespace JsonApiDotNetCoreExample
95
{
@@ -15,20 +11,11 @@ public sealed class NoDefaultPageSizeStartup : Startup
1511
{
1612
public NoDefaultPageSizeStartup(IWebHostEnvironment env) : base(env) { }
1713

18-
public override void ConfigureServices(IServiceCollection services)
14+
protected override void ConfigureJsonApiOptions(JsonApiOptions options)
1915
{
20-
var mvcBuilder = services.AddMvcCore();
21-
services
22-
.AddDbContext<AppDbContext>(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient)
23-
.AddJsonApi(options => {
24-
options.Namespace = "api/v1";
25-
options.IncludeTotalRecordCount = true;
26-
options.LoadDatabaseValues = true;
27-
options.AllowClientGeneratedIds = true;
28-
options.DefaultPageSize = 0;
29-
},
30-
discovery => discovery.AddAssembly(Assembly.Load(nameof(JsonApiDotNetCoreExample))),
31-
mvcBuilder: mvcBuilder);
16+
base.ConfigureJsonApiOptions(options);
17+
18+
options.DefaultPageSize = 0;
3219
}
3320
}
3421
}

src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
using Microsoft.EntityFrameworkCore;
77
using JsonApiDotNetCore.Extensions;
88
using System;
9+
using JsonApiDotNetCore.Configuration;
910
using JsonApiDotNetCore.Query;
1011
using JsonApiDotNetCoreExample.Services;
12+
using Newtonsoft.Json.Converters;
1113

1214
namespace JsonApiDotNetCoreExample
1315
{
@@ -37,21 +39,24 @@ public virtual void ConfigureServices(IServiceCollection services)
3739
.EnableSensitiveDataLogging()
3840
.UseNpgsql(GetDbConnectionString(), innerOptions => innerOptions.SetPostgresVersion(new Version(9,6)));
3941
}, ServiceLifetime.Transient)
40-
.AddJsonApi(options =>
41-
{
42-
options.IncludeExceptionStackTraceInErrors = true;
43-
options.Namespace = "api/v1";
44-
options.DefaultPageSize = 5;
45-
options.IncludeTotalRecordCount = true;
46-
options.LoadDatabaseValues = true;
47-
options.ValidateModelState = true;
48-
options.EnableResourceHooks = true;
49-
},
50-
discovery => discovery.AddCurrentAssembly());
42+
.AddJsonApi(ConfigureJsonApiOptions, discovery => discovery.AddCurrentAssembly());
43+
5144
// once all tests have been moved to WebApplicationFactory format we can get rid of this line below
5245
services.AddClientSerialization();
5346
}
5447

48+
protected virtual void ConfigureJsonApiOptions(JsonApiOptions options)
49+
{
50+
options.IncludeExceptionStackTraceInErrors = true;
51+
options.Namespace = "api/v1";
52+
options.DefaultPageSize = 5;
53+
options.IncludeTotalRecordCount = true;
54+
options.LoadDatabaseValues = true;
55+
options.ValidateModelState = true;
56+
options.EnableResourceHooks = true;
57+
options.SerializerSettings.Converters.Add(new StringEnumConverter());
58+
}
59+
5560
public void Configure(
5661
IApplicationBuilder app,
5762
AppDbContext context)

src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using JsonApiDotNetCore.Graph;
32
using JsonApiDotNetCore.Internal;
43
using JsonApiDotNetCore.Internal.Contracts;
54
using JsonApiDotNetCore.Models;
@@ -19,7 +18,6 @@ public interface IResourceGraphBuilder
1918
/// <param name="pluralizedTypeName">
2019
/// The pluralized name that should be exposed by the API.
2120
/// If nothing is specified, the configured name formatter will be used.
22-
/// See <see cref="IResourceNameFormatter" />.
2321
/// </param>
2422
IResourceGraphBuilder AddResource<TResource>(string pluralizedTypeName = null) where TResource : class, IIdentifiable<int>;
2523
/// <summary>
@@ -30,7 +28,6 @@ public interface IResourceGraphBuilder
3028
/// <param name="pluralizedTypeName">
3129
/// The pluralized name that should be exposed by the API.
3230
/// If nothing is specified, the configured name formatter will be used.
33-
/// See <see cref="IResourceNameFormatter" />.
3431
/// </param>
3532
IResourceGraphBuilder AddResource<TResource, TId>(string pluralizedTypeName = null) where TResource : class, IIdentifiable<TId>;
3633
/// <summary>
@@ -41,7 +38,6 @@ public interface IResourceGraphBuilder
4138
/// <param name="pluralizedTypeName">
4239
/// The pluralized name that should be exposed by the API.
4340
/// If nothing is specified, the configured name formatter will be used.
44-
/// See <see cref="IResourceNameFormatter" />.
4541
/// </param>
4642
IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null);
4743
}

src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace JsonApiDotNetCore.Builders
3030
/// </summary>
3131
public class JsonApiApplicationBuilder
3232
{
33-
public readonly JsonApiOptions JsonApiOptions = new JsonApiOptions();
33+
private readonly JsonApiOptions _options = new JsonApiOptions();
3434
internal IResourceGraphBuilder _resourceGraphBuilder;
3535
internal bool _usesDbContext;
3636
internal readonly IServiceCollection _services;
@@ -46,13 +46,13 @@ public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mv
4646
/// <summary>
4747
/// Executes the action provided by the user to configure <see cref="JsonApiOptions"/>
4848
/// </summary>
49-
public void ConfigureJsonApiOptions(Action<JsonApiOptions> configureOptions) => configureOptions(JsonApiOptions);
49+
public void ConfigureJsonApiOptions(Action<JsonApiOptions> configureOptions) => configureOptions(_options);
5050

5151
/// <summary>
52-
/// Configures built-in .net core MVC (things like middleware, routing). Most of this configuration can be adjusted for the developers need.
52+
/// Configures built-in .NET Core MVC (things like middleware, routing). Most of this configuration can be adjusted for the developers' need.
5353
/// Before calling .AddJsonApi(), a developer can register their own implementation of the following services to customize startup:
5454
/// <see cref="IResourceGraphBuilder"/>, <see cref="IServiceDiscoveryFacade"/>, <see cref="IJsonApiExceptionFilterProvider"/>,
55-
/// <see cref="IJsonApiTypeMatchFilterProvider"/>, <see cref="IJsonApiRoutingConvention"/> and <see cref="IResourceNameFormatter"/>.
55+
/// <see cref="IJsonApiTypeMatchFilterProvider"/> and <see cref="IJsonApiRoutingConvention"/>.
5656
/// </summary>
5757
public void ConfigureMvc()
5858
{
@@ -76,7 +76,7 @@ public void ConfigureMvc()
7676
options.Conventions.Insert(0, routingConvention);
7777
});
7878

79-
if (JsonApiOptions.ValidateModelState)
79+
if (_options.ValidateModelState)
8080
{
8181
_mvcBuilder.AddDataAnnotations();
8282
}
@@ -144,7 +144,7 @@ public void ConfigureServices()
144144
_services.AddScoped(typeof(IResourceQueryService<,>), typeof(DefaultResourceService<,>));
145145
_services.AddScoped(typeof(IResourceCommandService<,>), typeof(DefaultResourceService<,>));
146146

147-
_services.AddSingleton<ILinksConfiguration>(JsonApiOptions);
147+
_services.AddSingleton<ILinksConfiguration>(_options);
148148
_services.AddSingleton(resourceGraph);
149149
_services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
150150
_services.AddSingleton<IResourceContextProvider>(resourceGraph);
@@ -165,7 +165,7 @@ public void ConfigureServices()
165165

166166
AddServerSerialization();
167167
AddQueryParameterServices();
168-
if (JsonApiOptions.EnableResourceHooks)
168+
if (_options.EnableResourceHooks)
169169
AddResourceHooks();
170170

171171
_services.AddScoped<IInverseRelationships, InverseRelationships>();
@@ -178,16 +178,16 @@ private void AddQueryParameterServices()
178178
_services.AddScoped<ISortService, SortService>();
179179
_services.AddScoped<ISparseFieldsService, SparseFieldsService>();
180180
_services.AddScoped<IPageService, PageService>();
181-
_services.AddScoped<IOmitDefaultService, OmitDefaultService>();
182-
_services.AddScoped<IOmitNullService, OmitNullService>();
181+
_services.AddScoped<IDefaultsService, DefaultsService>();
182+
_services.AddScoped<INullsService, NullsService>();
183183

184184
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IIncludeService>());
185185
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IFilterService>());
186186
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<ISortService>());
187187
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<ISparseFieldsService>());
188188
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IPageService>());
189-
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IOmitDefaultService>());
190-
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IOmitNullService>());
189+
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IDefaultsService>());
190+
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<INullsService>());
191191
}
192192

193193
private void AddResourceHooks()
@@ -214,8 +214,7 @@ private void AddServerSerialization()
214214

215215
private void RegisterJsonApiStartupServices()
216216
{
217-
_services.AddSingleton<IJsonApiOptions>(JsonApiOptions);
218-
_services.TryAddSingleton<IResourceNameFormatter>(new CamelCaseFormatter());
217+
_services.AddSingleton<IJsonApiOptions>(_options);
219218
_services.TryAddSingleton<IJsonApiRoutingConvention, DefaultRoutingConvention>();
220219
_services.TryAddSingleton<IResourceGraphBuilder, ResourceGraphBuilder>();
221220
_services.TryAddSingleton<IServiceDiscoveryFacade>(sp => new ServiceDiscoveryFacade(_services, sp.GetRequiredService<IResourceGraphBuilder>()));

0 commit comments

Comments
 (0)