Skip to content

Commit e2c94d3

Browse files
author
Bart Koelman
committed
Converted core code to use System.Text.Json
- Added various converters to steer JsonSerializer in the right direction - JsonApiDotNetCore.Serialization.Objects - Removed inheritance in JsonApiDotNetCore.Serialization.Objects, so we're in control of element write order - Moved "meta" to the end in all types (it is secondary information) - Consistently set IgnoreCondition on all properties, so we don't need to override global options anymore
1 parent 16ec48a commit e2c94d3

File tree

103 files changed

+1455
-889
lines changed

Some content is hidden

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

103 files changed

+1455
-889
lines changed

benchmarks/DependencyFactory.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ internal sealed class DependencyFactory
88
public IResourceGraph CreateResourceGraph(IJsonApiOptions options)
99
{
1010
var builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance);
11+
1112
builder.Add<BenchmarkResource>(BenchmarkResourcePublicNames.Type);
13+
builder.Add<SubResource>();
14+
1215
return builder.Build();
1316
}
1417
}

benchmarks/Serialization/JsonApiDeserializerBenchmarks.cs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,26 @@
1-
using System;
2-
using System.Collections.Generic;
31
using System.ComponentModel.Design;
2+
using System.Text.Json;
43
using BenchmarkDotNet.Attributes;
54
using JsonApiDotNetCore.Configuration;
65
using JsonApiDotNetCore.Middleware;
76
using JsonApiDotNetCore.Resources;
87
using JsonApiDotNetCore.Serialization;
9-
using JsonApiDotNetCore.Serialization.Objects;
108
using Microsoft.AspNetCore.Http;
11-
using Newtonsoft.Json;
129

1310
namespace Benchmarks.Serialization
1411
{
1512
// ReSharper disable once ClassCanBeSealed.Global
1613
[MarkdownExporter]
1714
public class JsonApiDeserializerBenchmarks
1815
{
19-
private static readonly string Content = JsonConvert.SerializeObject(new Document
16+
private static readonly string RequestBody = JsonSerializer.Serialize(new
2017
{
21-
Data = new ResourceObject
18+
data = new
2219
{
23-
Type = BenchmarkResourcePublicNames.Type,
24-
Id = "1",
25-
Attributes = new Dictionary<string, object>
20+
type = BenchmarkResourcePublicNames.Type,
21+
id = "1",
22+
attributes = new
2623
{
27-
["name"] = Guid.NewGuid().ToString()
2824
}
2925
}
3026
});
@@ -55,7 +51,7 @@ public JsonApiDeserializerBenchmarks()
5551
[Benchmark]
5652
public object DeserializeSimpleObject()
5753
{
58-
return _jsonApiDeserializer.Deserialize(Content);
54+
return _jsonApiDeserializer.Deserialize(RequestBody);
5955
}
6056
}
6157
}

src/Examples/GettingStarted/Startup.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Microsoft.AspNetCore.Builder;
66
using Microsoft.EntityFrameworkCore;
77
using Microsoft.Extensions.DependencyInjection;
8-
using Newtonsoft.Json;
98

109
namespace GettingStarted
1110
{
@@ -21,7 +20,7 @@ public void ConfigureServices(IServiceCollection services)
2120
options.Namespace = "api";
2221
options.UseRelativeLinks = true;
2322
options.IncludeTotalResourceCount = true;
24-
options.SerializerSettings.Formatting = Formatting.Indented;
23+
options.SerializerOptions.WriteIndented = true;
2524
});
2625
}
2726

src/Examples/JsonApiDotNetCoreExample/Startup.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Text.Json.Serialization;
23
using JsonApiDotNetCore.Configuration;
34
using JsonApiDotNetCore.Diagnostics;
45
using JsonApiDotNetCoreExample.Data;
@@ -9,8 +10,6 @@
910
using Microsoft.Extensions.Configuration;
1011
using Microsoft.Extensions.DependencyInjection;
1112
using Microsoft.Extensions.Logging;
12-
using Newtonsoft.Json;
13-
using Newtonsoft.Json.Converters;
1413

1514
namespace JsonApiDotNetCoreExample
1615
{
@@ -52,8 +51,8 @@ public void ConfigureServices(IServiceCollection services)
5251
options.UseRelativeLinks = true;
5352
options.ValidateModelState = true;
5453
options.IncludeTotalResourceCount = true;
55-
options.SerializerSettings.Formatting = Formatting.Indented;
56-
options.SerializerSettings.Converters.Add(new StringEnumConverter());
54+
options.SerializerOptions.WriteIndented = true;
55+
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
5756
#if DEBUG
5857
options.IncludeExceptionStackTraceInErrors = true;
5958
#endif

src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Text.Json;
44
using JsonApiDotNetCore.Resources.Annotations;
55
using JsonApiDotNetCore.Serialization.Objects;
6-
using Newtonsoft.Json;
76

87
namespace JsonApiDotNetCore.Configuration
98
{
@@ -136,11 +135,8 @@ public interface IJsonApiOptions
136135
/// </summary>
137136
IsolationLevel? TransactionIsolationLevel { get; }
138137

139-
JsonSerializerSettings SerializerSettings { get; }
140-
141138
/// <summary>
142-
/// Specifies the settings that are used by the <see cref="System.Text.Json.JsonSerializer" />. Note that at some places a few settings are ignored, to
143-
/// ensure JSON:API spec compliance.
139+
/// Enables to customize the settings that are used by the <see cref="JsonSerializer" />.
144140
/// </summary>
145141
/// <example>
146142
/// The next example sets the naming convention to camel casing.
@@ -150,5 +146,15 @@ public interface IJsonApiOptions
150146
/// ]]></code>
151147
/// </example>
152148
JsonSerializerOptions SerializerOptions { get; }
149+
150+
/// <summary>
151+
/// Gets the settings used for deserializing request bodies. This value is based on <see cref="SerializerOptions" /> and is intended for internal use.
152+
/// </summary>
153+
JsonSerializerOptions SerializerReadOptions { get; }
154+
155+
/// <summary>
156+
/// Gets the settings used for serializing response bodies. This value is based on <see cref="SerializerOptions" /> and is intended for internal use.
157+
/// </summary>
158+
JsonSerializerOptions SerializerWriteOptions { get; }
153159
}
154160
}

src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using JsonApiDotNetCore.Resources;
1313
using JsonApiDotNetCore.Serialization;
1414
using JsonApiDotNetCore.Serialization.Building;
15+
using JsonApiDotNetCore.Serialization.JsonConverters;
1516
using JsonApiDotNetCore.Services;
1617
using Microsoft.AspNetCore.Http;
1718
using Microsoft.AspNetCore.Mvc;
@@ -87,6 +88,9 @@ public void AddResourceGraph(ICollection<Type> dbContextTypes, Action<ResourceGr
8788
configureResourceGraph?.Invoke(_resourceGraphBuilder);
8889

8990
IResourceGraph resourceGraph = _resourceGraphBuilder.Build();
91+
92+
_options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
93+
9094
_services.AddSingleton(resourceGraph);
9195
}
9296

src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1+
using System;
12
using System.Data;
3+
using System.Text.Encodings.Web;
24
using System.Text.Json;
5+
using System.Threading;
36
using JetBrains.Annotations;
47
using JsonApiDotNetCore.Resources.Annotations;
5-
using Newtonsoft.Json;
6-
using Newtonsoft.Json.Serialization;
8+
using JsonApiDotNetCore.Serialization.JsonConverters;
79

810
namespace JsonApiDotNetCore.Configuration
911
{
1012
/// <inheritdoc />
1113
[PublicAPI]
1214
public sealed class JsonApiOptions : IJsonApiOptions
1315
{
16+
private Lazy<JsonSerializerOptions> _lazySerializerWriteOptions;
17+
private Lazy<JsonSerializerOptions> _lazySerializerReadOptions;
18+
19+
/// <inheritdoc />
20+
JsonSerializerOptions IJsonApiOptions.SerializerReadOptions => _lazySerializerReadOptions.Value;
21+
22+
/// <inheritdoc />
23+
JsonSerializerOptions IJsonApiOptions.SerializerWriteOptions => _lazySerializerWriteOptions.Value;
24+
1425
// Workaround for https://github.com/dotnet/efcore/issues/21026
1526
internal bool DisableTopPagination { get; set; }
1627
internal bool DisableChildrenPagination { get; set; }
@@ -72,19 +83,38 @@ public sealed class JsonApiOptions : IJsonApiOptions
7283
/// <inheritdoc />
7384
public IsolationLevel? TransactionIsolationLevel { get; set; }
7485

75-
public JsonSerializerSettings SerializerSettings { get; } = new()
86+
/// <inheritdoc />
87+
public JsonSerializerOptions SerializerOptions { get; } = new()
7688
{
77-
ContractResolver = new DefaultContractResolver
89+
// These are the options common to serialization and deserialization.
90+
// At runtime, we actually use SerializerReadOptions and SerializerWriteOptions, which are customized copies of these settings,
91+
// to overcome the limitation in System.Text.Json that the JsonPath is incorrect when using custom converters.
92+
// Therefore we try to avoid using custom converters has much as possible.
93+
// https://github.com/Tarmil/FSharp.SystemTextJson/issues/37
94+
// https://github.com/dotnet/runtime/issues/50205#issuecomment-808401245
95+
96+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
97+
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
98+
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
99+
Converters =
78100
{
79-
NamingStrategy = new CamelCaseNamingStrategy()
101+
new SingleOrManyDataConverterFactory()
80102
}
81103
};
82104

83-
/// <inheritdoc />
84-
public JsonSerializerOptions SerializerOptions { get; } = new()
105+
public JsonApiOptions()
85106
{
86-
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
87-
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
88-
};
107+
_lazySerializerReadOptions =
108+
new Lazy<JsonSerializerOptions>(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.PublicationOnly);
109+
110+
_lazySerializerWriteOptions = new Lazy<JsonSerializerOptions>(() => new JsonSerializerOptions(SerializerOptions)
111+
{
112+
Converters =
113+
{
114+
new WriteOnlyDocumentConverter(),
115+
new WriteOnlyRelationshipObjectConverter()
116+
}
117+
}, LazyThreadSafetyMode.PublicationOnly);
118+
}
89119
}
90120
}

src/JsonApiDotNetCore/Errors/InvalidModelStateException.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using JetBrains.Annotations;
99
using JsonApiDotNetCore.Controllers;
1010
using JsonApiDotNetCore.Resources.Annotations;
11-
using JsonApiDotNetCore.Serialization;
1211
using JsonApiDotNetCore.Serialization.Objects;
1312
using Microsoft.AspNetCore.Mvc.ModelBinding;
1413

@@ -117,8 +116,10 @@ private static ErrorObject FromModelError(ModelError modelError, string attribut
117116

118117
if (includeExceptionStackTraceInErrors && modelError.Exception != null)
119118
{
119+
string[] stackTraceLines = modelError.Exception.Demystify().ToString().Split(Environment.NewLine);
120+
120121
error.Meta ??= new Dictionary<string, object>();
121-
error.Meta.IncludeExceptionStackTrace(modelError.Exception.Demystify());
122+
error.Meta["StackTrace"] = stackTraceLines;
122123
}
123124

124125
return error;

src/JsonApiDotNetCore/JsonApiDotNetCore.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="$(EFCoreVersion)" />
2727
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(EFCoreVersion)" />
2828
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
29-
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
3029
<PackageReference Include="SauceControl.InheritDoc" Version="1.3.0" PrivateAssets="All" />
3130
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
3231
</ItemGroup>

src/JsonApiDotNetCore/Middleware/ExceptionHandler.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using JetBrains.Annotations;
77
using JsonApiDotNetCore.Configuration;
88
using JsonApiDotNetCore.Errors;
9-
using JsonApiDotNetCore.Serialization;
109
using JsonApiDotNetCore.Serialization.Objects;
1110
using Microsoft.Extensions.Logging;
1211

@@ -102,8 +101,10 @@ private void ApplyOptions(ErrorObject error, Exception exception)
102101

103102
if (resultException != null && _options.IncludeExceptionStackTraceInErrors)
104103
{
104+
string[] stackTraceLines = resultException.ToString().Split(Environment.NewLine);
105+
105106
error.Meta ??= new Dictionary<string, object>();
106-
error.Meta.IncludeExceptionStackTrace(resultException);
107+
error.Meta["StackTrace"] = stackTraceLines;
107108
}
108109
}
109110
}

0 commit comments

Comments
 (0)