diff --git a/benchmarks/Benchmarks.csproj b/benchmarks/Benchmarks.csproj index 4bde435c15..f461a4831b 100644 --- a/benchmarks/Benchmarks.csproj +++ b/benchmarks/Benchmarks.csproj @@ -1,7 +1,8 @@ - + Exe $(TargetFrameworkName) + true @@ -10,6 +11,5 @@ - diff --git a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs index d28684e27b..99adce73cb 100644 --- a/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs +++ b/benchmarks/Deserialization/OperationsDeserializationBenchmarks.cs @@ -7,6 +7,7 @@ namespace Benchmarks.Deserialization; [MarkdownExporter] +[MemoryDiagnoser] // ReSharper disable once ClassCanBeSealed.Global public class OperationsDeserializationBenchmarks : DeserializationBenchmarkBase { diff --git a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs index 23a6205bf5..e503a329bb 100644 --- a/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs +++ b/benchmarks/Deserialization/ResourceDeserializationBenchmarks.cs @@ -7,6 +7,7 @@ namespace Benchmarks.Deserialization; [MarkdownExporter] +[MemoryDiagnoser] // ReSharper disable once ClassCanBeSealed.Global public class ResourceDeserializationBenchmarks : DeserializationBenchmarkBase { diff --git a/benchmarks/QueryString/QueryStringParserBenchmarks.cs b/benchmarks/QueryString/QueryStringParserBenchmarks.cs index efa4f12659..4218c2e3dc 100644 --- a/benchmarks/QueryString/QueryStringParserBenchmarks.cs +++ b/benchmarks/QueryString/QueryStringParserBenchmarks.cs @@ -1,13 +1,12 @@ using System.ComponentModel.Design; using BenchmarkDotNet.Attributes; +using Benchmarks.Tools; using JsonApiDotNetCore; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.QueryStrings; using JsonApiDotNetCore.QueryStrings.Internal; using JsonApiDotNetCore.Resources; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging.Abstractions; namespace Benchmarks.QueryString; @@ -71,31 +70,9 @@ public void DescendingSort() [Benchmark] public void ComplexQuery() { - Run(100, () => - { - const string queryString = - "?filter[alt-attr-name]=abc,eq:abc&sort=-alt-attr-name&include=child&page[size]=1&fields[alt-resource-name]=alt-attr-name"; - - _queryStringAccessor.SetQueryString(queryString); - _queryStringReader.ReadAll(null); - }); - } - - private void Run(int iterations, Action action) - { - for (int index = 0; index < iterations; index++) - { - action(); - } - } - - private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor - { - public IQueryCollection Query { get; private set; } = new QueryCollection(); + const string queryString = "?filter[alt-attr-name]=abc,eq:abc&sort=-alt-attr-name&include=child&page[size]=1&fields[alt-resource-name]=alt-attr-name"; - public void SetQueryString(string queryString) - { - Query = new QueryCollection(QueryHelpers.ParseQuery(queryString)); - } + _queryStringAccessor.SetQueryString(queryString); + _queryStringReader.ReadAll(null); } } diff --git a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs index 7076ca5cb8..471c9604c7 100644 --- a/benchmarks/Serialization/OperationsSerializationBenchmarks.cs +++ b/benchmarks/Serialization/OperationsSerializationBenchmarks.cs @@ -9,6 +9,7 @@ namespace Benchmarks.Serialization; [MarkdownExporter] +[MemoryDiagnoser] // ReSharper disable once ClassCanBeSealed.Global public class OperationsSerializationBenchmarks : SerializationBenchmarkBase { diff --git a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs index 12f5c2e788..a985bd5936 100644 --- a/benchmarks/Serialization/ResourceSerializationBenchmarks.cs +++ b/benchmarks/Serialization/ResourceSerializationBenchmarks.cs @@ -12,6 +12,7 @@ namespace Benchmarks.Serialization; [MarkdownExporter] +[MemoryDiagnoser] // ReSharper disable once ClassCanBeSealed.Global public class ResourceSerializationBenchmarks : SerializationBenchmarkBase { diff --git a/benchmarks/Serialization/SerializationBenchmarkBase.cs b/benchmarks/Serialization/SerializationBenchmarkBase.cs index e1bcb10843..d9cfefd0b6 100644 --- a/benchmarks/Serialization/SerializationBenchmarkBase.cs +++ b/benchmarks/Serialization/SerializationBenchmarkBase.cs @@ -1,18 +1,14 @@ -using System.Collections.Immutable; using System.Text.Json; using System.Text.Json.Serialization; +using Benchmarks.Tools; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Queries; -using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Queries.Internal; -using JsonApiDotNetCore.QueryStrings; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCore.Serialization.Response; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging.Abstractions; namespace Benchmarks.Serialization; @@ -45,9 +41,9 @@ protected SerializationBenchmarkBase() // ReSharper restore VirtualMemberCallInConstructor var linkBuilder = new FakeLinkBuilder(); - var metaBuilder = new FakeMetaBuilder(); + var metaBuilder = new NoMetaBuilder(); IQueryConstraintProvider[] constraintProviders = Array.Empty(); - var resourceDefinitionAccessor = new FakeResourceDefinitionAccessor(); + var resourceDefinitionAccessor = new NeverResourceDefinitionAccessor(); var sparseFieldSetCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor); var requestQueryStringAccessor = new FakeRequestQueryStringAccessor(); @@ -122,141 +118,4 @@ public sealed class OutgoingResource : Identifiable [HasMany] public ISet Multi5 { get; set; } = null!; } - - private sealed class FakeResourceDefinitionAccessor : IResourceDefinitionAccessor - { - public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes) - { - return existingIncludes; - } - - public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter) - { - return existingFilter; - } - - public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort) - { - return existingSort; - } - - public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination) - { - return existingPagination; - } - - public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet) - { - return existingSparseFieldSet; - } - - public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName) - { - return null; - } - - public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance) - { - return null; - } - - public Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship, - IIdentifiable? rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.FromResult(rightResourceId); - } - - public Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - WriteOperationKind writeOperation, CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public Task OnAddToRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public Task OnRemoveFromRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, - CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) - where TResource : class, IIdentifiable - { - return Task.CompletedTask; - } - - public void OnDeserialize(IIdentifiable resource) - { - } - - public void OnSerialize(IIdentifiable resource) - { - } - } - - private sealed class FakeLinkBuilder : ILinkBuilder - { - public TopLevelLinks GetTopLevelLinks() - { - return new TopLevelLinks - { - Self = "TopLevel:Self" - }; - } - - public ResourceLinks GetResourceLinks(ResourceType resourceType, IIdentifiable resource) - { - return new ResourceLinks - { - Self = "Resource:Self" - }; - } - - public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource) - { - return new RelationshipLinks - { - Self = "Relationship:Self", - Related = "Relationship:Related" - }; - } - } - - private sealed class FakeMetaBuilder : IMetaBuilder - { - public void Add(IDictionary values) - { - } - - public IDictionary? Build() - { - return null; - } - } - - private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor - { - public IQueryCollection Query { get; } = new QueryCollection(0); - } } diff --git a/benchmarks/Tools/FakeLinkBuilder.cs b/benchmarks/Tools/FakeLinkBuilder.cs new file mode 100644 index 0000000000..3468237507 --- /dev/null +++ b/benchmarks/Tools/FakeLinkBuilder.cs @@ -0,0 +1,39 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; +using JsonApiDotNetCore.Serialization.Objects; +using JsonApiDotNetCore.Serialization.Response; +using Microsoft.AspNetCore.Http; + +namespace Benchmarks.Tools; + +/// +/// Renders hard-coded fake links, without depending on . +/// +internal sealed class FakeLinkBuilder : ILinkBuilder +{ + public TopLevelLinks GetTopLevelLinks() + { + return new TopLevelLinks + { + Self = "TopLevel:Self" + }; + } + + public ResourceLinks GetResourceLinks(ResourceType resourceType, IIdentifiable resource) + { + return new ResourceLinks + { + Self = "Resource:Self" + }; + } + + public RelationshipLinks GetRelationshipLinks(RelationshipAttribute relationship, IIdentifiable leftResource) + { + return new RelationshipLinks + { + Self = "Relationship:Self", + Related = "Relationship:Related" + }; + } +} diff --git a/benchmarks/Tools/FakeRequestQueryStringAccessor.cs b/benchmarks/Tools/FakeRequestQueryStringAccessor.cs new file mode 100644 index 0000000000..8b2b5540a1 --- /dev/null +++ b/benchmarks/Tools/FakeRequestQueryStringAccessor.cs @@ -0,0 +1,18 @@ +using JsonApiDotNetCore.QueryStrings; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; + +namespace Benchmarks.Tools; + +/// +/// Enables to inject a query string, instead of obtaining it from . +/// +internal sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor +{ + public IQueryCollection Query { get; private set; } = new QueryCollection(); + + public void SetQueryString(string queryString) + { + Query = new QueryCollection(QueryHelpers.ParseQuery(queryString)); + } +} diff --git a/benchmarks/Tools/NeverResourceDefinitionAccessor.cs b/benchmarks/Tools/NeverResourceDefinitionAccessor.cs new file mode 100644 index 0000000000..6e93519dae --- /dev/null +++ b/benchmarks/Tools/NeverResourceDefinitionAccessor.cs @@ -0,0 +1,103 @@ +using System.Collections.Immutable; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Queries.Expressions; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace Benchmarks.Tools; + +/// +/// Never calls into instances. +/// +internal sealed class NeverResourceDefinitionAccessor : IResourceDefinitionAccessor +{ + public IImmutableSet OnApplyIncludes(ResourceType resourceType, IImmutableSet existingIncludes) + { + return existingIncludes; + } + + public FilterExpression? OnApplyFilter(ResourceType resourceType, FilterExpression? existingFilter) + { + return existingFilter; + } + + public SortExpression? OnApplySort(ResourceType resourceType, SortExpression? existingSort) + { + return existingSort; + } + + public PaginationExpression? OnApplyPagination(ResourceType resourceType, PaginationExpression? existingPagination) + { + return existingPagination; + } + + public SparseFieldSetExpression? OnApplySparseFieldSet(ResourceType resourceType, SparseFieldSetExpression? existingSparseFieldSet) + { + return existingSparseFieldSet; + } + + public object? GetQueryableHandlerForQueryStringParameter(Type resourceClrType, string parameterName) + { + return null; + } + + public IDictionary? GetMeta(ResourceType resourceType, IIdentifiable resourceInstance) + { + return null; + } + + public Task OnPrepareWriteAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public Task OnSetToOneRelationshipAsync(TResource leftResource, HasOneAttribute hasOneRelationship, + IIdentifiable? rightResourceId, WriteOperationKind writeOperation, CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.FromResult(rightResourceId); + } + + public Task OnSetToManyRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, + WriteOperationKind writeOperation, CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public Task OnAddToRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, + CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public Task OnRemoveFromRelationshipAsync(TResource leftResource, HasManyAttribute hasManyRelationship, ISet rightResourceIds, + CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public Task OnWritingAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public Task OnWriteSucceededAsync(TResource resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) + where TResource : class, IIdentifiable + { + return Task.CompletedTask; + } + + public void OnDeserialize(IIdentifiable resource) + { + } + + public void OnSerialize(IIdentifiable resource) + { + } +} diff --git a/benchmarks/Tools/NoMetaBuilder.cs b/benchmarks/Tools/NoMetaBuilder.cs new file mode 100644 index 0000000000..db3ed7857e --- /dev/null +++ b/benchmarks/Tools/NoMetaBuilder.cs @@ -0,0 +1,18 @@ +using JsonApiDotNetCore.Serialization.Response; + +namespace Benchmarks.Tools; + +/// +/// Doesn't produce any top-level meta. +/// +internal sealed class NoMetaBuilder : IMetaBuilder +{ + public void Add(IDictionary values) + { + } + + public IDictionary? Build() + { + return null; + } +} diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index eda6374acf..2e7cc54282 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -11,8 +11,8 @@ namespace JsonApiDotNetCore.Configuration; [PublicAPI] public sealed class JsonApiOptions : IJsonApiOptions { - private Lazy _lazySerializerWriteOptions; - private Lazy _lazySerializerReadOptions; + private readonly Lazy _lazySerializerWriteOptions; + private readonly Lazy _lazySerializerReadOptions; /// JsonSerializerOptions IJsonApiOptions.SerializerReadOptions => _lazySerializerReadOptions.Value; @@ -110,7 +110,8 @@ static JsonApiOptions() public JsonApiOptions() { - _lazySerializerReadOptions = new Lazy(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.PublicationOnly); + _lazySerializerReadOptions = + new Lazy(() => new JsonSerializerOptions(SerializerOptions), LazyThreadSafetyMode.ExecutionAndPublication); _lazySerializerWriteOptions = new Lazy(() => new JsonSerializerOptions(SerializerOptions) { @@ -119,6 +120,6 @@ public JsonApiOptions() new WriteOnlyDocumentConverter(), new WriteOnlyRelationshipObjectConverter() } - }, LazyThreadSafetyMode.PublicationOnly); + }, LazyThreadSafetyMode.ExecutionAndPublication); } } diff --git a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs index 94507750da..2e15e6ae9a 100644 --- a/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs +++ b/src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs @@ -72,7 +72,7 @@ public async Task InvokeAsync(HttpContext httpContext, IControllerResourceMappin return; } - SetupOperationsRequest((JsonApiRequest)request, options, httpContext.Request); + SetupOperationsRequest((JsonApiRequest)request); httpContext.RegisterJsonApiRequest(); } @@ -280,7 +280,7 @@ private static bool IsRouteForOperations(RouteValueDictionary routeValues) return actionName == "PostOperations"; } - private static void SetupOperationsRequest(JsonApiRequest request, IJsonApiOptions options, HttpRequest httpRequest) + private static void SetupOperationsRequest(JsonApiRequest request) { request.IsReadOnly = false; request.Kind = EndpointKind.AtomicOperations; diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs index a453921989..14d2f1ec15 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs @@ -202,7 +202,7 @@ public string Path var pathBuilder = new StringBuilder(); IncludeTreeNode? parent = this; - while (parent is { Relationship: not HiddenRootRelationship }) + while (parent is { Relationship: not HiddenRootRelationshipAttribute }) { pathBuilder.Insert(0, pathBuilder.Length > 0 ? $"{parent.Relationship.PublicName}." : parent.Relationship.PublicName); parent = parent._parent; @@ -220,7 +220,7 @@ private IncludeTreeNode(RelationshipAttribute relationship, IncludeTreeNode? par public static IncludeTreeNode CreateRoot(ResourceType resourceType) { - var relationship = new HiddenRootRelationship(resourceType); + var relationship = new HiddenRootRelationshipAttribute(resourceType); return new IncludeTreeNode(relationship, null); } @@ -242,7 +242,7 @@ public IncludeExpression ToExpression() { IncludeElementExpression element = ToElementExpression(); - if (element.Relationship is HiddenRootRelationship) + if (element.Relationship is HiddenRootRelationshipAttribute) { return new IncludeExpression(element.Children); } @@ -262,9 +262,9 @@ public override string ToString() return include.ToFullString(); } - private sealed class HiddenRootRelationship : RelationshipAttribute + private sealed class HiddenRootRelationshipAttribute : RelationshipAttribute { - public HiddenRootRelationship(ResourceType rightType) + public HiddenRootRelationshipAttribute(ResourceType rightType) { ArgumentGuard.NotNull(rightType, nameof(rightType)); diff --git a/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs b/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs index 25e497c2c1..b842cace0e 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonConverters/SingleOrManyDataConverterFactory.cs @@ -26,9 +26,9 @@ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializer } private sealed class SingleOrManyDataConverter : JsonObjectConverter> - where T : class, IResourceIdentity, new() + where T : ResourceIdentifierObject, new() { - public override SingleOrManyData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions serializerOptions) + public override SingleOrManyData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var objects = new List(); bool isManyData = false; @@ -54,7 +54,7 @@ public override SingleOrManyData Read(ref Utf8JsonReader reader, Type typeToC } case JsonTokenType.StartObject: { - var resourceObject = ReadSubTree(ref reader, serializerOptions); + var resourceObject = ReadSubTree(ref reader, options); objects.Add(resourceObject); break; } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/AtomicReference.cs b/src/JsonApiDotNetCore/Serialization/Objects/AtomicReference.cs index 01693d1db6..fcc56298c1 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/AtomicReference.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/AtomicReference.cs @@ -7,20 +7,8 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// See "ref" in https://jsonapi.org/ext/atomic/#operation-objects. /// [PublicAPI] -public sealed class AtomicReference : IResourceIdentity +public sealed class AtomicReference : ResourceIdentity { - [JsonPropertyName("type")] - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public string? Type { get; set; } - - [JsonPropertyName("id")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Id { get; set; } - - [JsonPropertyName("lid")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Lid { get; set; } - [JsonPropertyName("relationship")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Relationship { get; set; } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/IResourceIdentity.cs b/src/JsonApiDotNetCore/Serialization/Objects/IResourceIdentity.cs deleted file mode 100644 index c4b57f535f..0000000000 --- a/src/JsonApiDotNetCore/Serialization/Objects/IResourceIdentity.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace JsonApiDotNetCore.Serialization.Objects; - -public interface IResourceIdentity -{ - public string? Type { get; } - public string? Id { get; } - public string? Lid { get; } -} diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs b/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs index a1b8271cf7..20c30909ed 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentifierObject.cs @@ -7,21 +7,10 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// See https://jsonapi.org/format/1.1/#document-resource-identifier-objects. /// [PublicAPI] -public sealed class ResourceIdentifierObject : IResourceIdentity +public class ResourceIdentifierObject : ResourceIdentity { - [JsonPropertyName("type")] - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public string? Type { get; set; } - - [JsonPropertyName("id")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Id { get; set; } - - [JsonPropertyName("lid")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Lid { get; set; } - [JsonPropertyName("meta")] + [JsonPropertyOrder(100)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IDictionary? Meta { get; set; } } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentity.cs b/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentity.cs new file mode 100644 index 0000000000..41a3d951e6 --- /dev/null +++ b/src/JsonApiDotNetCore/Serialization/Objects/ResourceIdentity.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; +using JetBrains.Annotations; + +namespace JsonApiDotNetCore.Serialization.Objects; + +/// +/// Shared identity information for various JSON:API objects. +/// +[PublicAPI] +public abstract class ResourceIdentity +{ + [JsonPropertyName("type")] + [JsonPropertyOrder(-3)] + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public string? Type { get; set; } + + [JsonPropertyName("id")] + [JsonPropertyOrder(-2)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Id { get; set; } + + [JsonPropertyName("lid")] + [JsonPropertyOrder(-1)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Lid { get; set; } +} diff --git a/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs b/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs index 43b3b9616a..ed38a40f9a 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/ResourceObject.cs @@ -7,33 +7,20 @@ namespace JsonApiDotNetCore.Serialization.Objects; /// See https://jsonapi.org/format/1.1/#document-resource-objects. /// [PublicAPI] -public sealed class ResourceObject : IResourceIdentity +public sealed class ResourceObject : ResourceIdentifierObject { - [JsonPropertyName("type")] - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public string? Type { get; set; } - - [JsonPropertyName("id")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Id { get; set; } - - [JsonPropertyName("lid")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string? Lid { get; set; } - [JsonPropertyName("attributes")] + [JsonPropertyOrder(1)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IDictionary? Attributes { get; set; } [JsonPropertyName("relationships")] + [JsonPropertyOrder(2)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public IDictionary? Relationships { get; set; } [JsonPropertyName("links")] + [JsonPropertyOrder(3)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public ResourceLinks? Links { get; set; } - - [JsonPropertyName("meta")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public IDictionary? Meta { get; set; } } diff --git a/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs b/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs index 1d2f99e126..1126f84f26 100644 --- a/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs +++ b/src/JsonApiDotNetCore/Serialization/Objects/SingleOrManyData.cs @@ -13,7 +13,7 @@ namespace JsonApiDotNetCore.Serialization.Objects; public readonly struct SingleOrManyData // The "new()" constraint exists for parity with SingleOrManyDataConverterFactory, which creates empty instances // to ensure ManyValue never contains null items. - where T : class, IResourceIdentity, new() + where T : ResourceIdentifierObject, new() { public object? Value => ManyValue != null ? ManyValue : SingleValue; diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/BaseAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/BaseAdapter.cs index 64e2f6d53b..fb1111bea1 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/BaseAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/BaseAdapter.cs @@ -11,7 +11,7 @@ public abstract class BaseAdapter { [AssertionMethod] protected static void AssertHasData(SingleOrManyData data, RequestAdapterState state) - where T : class, IResourceIdentity, new() + where T : ResourceIdentifierObject, new() { if (!data.IsAssigned) { @@ -21,7 +21,7 @@ protected static void AssertHasData(SingleOrManyData data, RequestAdapterS [AssertionMethod] protected static void AssertDataHasSingleValue(SingleOrManyData data, bool allowNull, RequestAdapterState state) - where T : class, IResourceIdentity, new() + where T : ResourceIdentifierObject, new() { if (data.SingleValue == null) { @@ -44,7 +44,7 @@ protected static void AssertDataHasSingleValue(SingleOrManyData data, bool [AssertionMethod] protected static void AssertDataHasManyValue(SingleOrManyData data, RequestAdapterState state) - where T : class, IResourceIdentity, new() + where T : ResourceIdentifierObject, new() { if (data.ManyValue == null) { diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs index d163eb56d1..61c6cc1857 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityAdapter.cs @@ -25,7 +25,7 @@ protected ResourceIdentityAdapter(IResourceGraph resourceGraph, IResourceFactory _resourceFactory = resourceFactory; } - protected (IIdentifiable resource, ResourceType resourceType) ConvertResourceIdentity(IResourceIdentity identity, ResourceIdentityRequirements requirements, + protected (IIdentifiable resource, ResourceType resourceType) ConvertResourceIdentity(ResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) { ArgumentGuard.NotNull(identity, nameof(identity)); @@ -38,7 +38,7 @@ protected ResourceIdentityAdapter(IResourceGraph resourceGraph, IResourceFactory return (resource, resourceType); } - private ResourceType ResolveType(IResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) + private ResourceType ResolveType(ResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) { AssertHasType(identity.Type, state); @@ -93,7 +93,7 @@ private static void AssertIsCompatibleResourceType(ResourceType actual, Resource } } - private IIdentifiable CreateResource(IResourceIdentity identity, ResourceIdentityRequirements requirements, Type resourceClrType, RequestAdapterState state) + private IIdentifiable CreateResource(ResourceIdentity identity, ResourceIdentityRequirements requirements, Type resourceClrType, RequestAdapterState state) { if (state.Request.Kind != EndpointKind.AtomicOperations) { @@ -120,7 +120,7 @@ private IIdentifiable CreateResource(IResourceIdentity identity, ResourceIdentit return resource; } - private static void AssertHasNoLid(IResourceIdentity identity, RequestAdapterState state) + private static void AssertHasNoLid(ResourceIdentity identity, RequestAdapterState state) { if (identity.Lid != null) { @@ -129,7 +129,7 @@ private static void AssertHasNoLid(IResourceIdentity identity, RequestAdapterSta } } - private static void AssertNoIdWithLid(IResourceIdentity identity, RequestAdapterState state) + private static void AssertNoIdWithLid(ResourceIdentity identity, RequestAdapterState state) { if (identity.Id != null && identity.Lid != null) { @@ -137,7 +137,7 @@ private static void AssertNoIdWithLid(IResourceIdentity identity, RequestAdapter } } - private static void AssertHasIdOrLid(IResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) + private static void AssertHasIdOrLid(ResourceIdentity identity, ResourceIdentityRequirements requirements, RequestAdapterState state) { string? message = null; @@ -160,7 +160,7 @@ private static void AssertHasIdOrLid(IResourceIdentity identity, ResourceIdentit } } - private static void AssertHasNoId(IResourceIdentity identity, RequestAdapterState state) + private static void AssertHasNoId(ResourceIdentity identity, RequestAdapterState state) { if (identity.Id != null) { @@ -169,7 +169,7 @@ private static void AssertHasNoId(IResourceIdentity identity, RequestAdapterStat } } - private static void AssertSameIdValue(IResourceIdentity identity, string? expected, RequestAdapterState state) + private static void AssertSameIdValue(ResourceIdentity identity, string? expected, RequestAdapterState state) { if (expected != null && identity.Id != expected) { @@ -180,7 +180,7 @@ private static void AssertSameIdValue(IResourceIdentity identity, string? expect } } - private static void AssertSameLidValue(IResourceIdentity identity, string? expected, RequestAdapterState state) + private static void AssertSameLidValue(ResourceIdentity identity, string? expected, RequestAdapterState state) { if (expected != null && identity.Lid != expected) { @@ -191,7 +191,7 @@ private static void AssertSameLidValue(IResourceIdentity identity, string? expec } } - private void AssignStringId(IResourceIdentity identity, IIdentifiable resource, RequestAdapterState state) + private void AssignStringId(ResourceIdentity identity, IIdentifiable resource, RequestAdapterState state) { if (identity.Id != null) { diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs index 11db5e8ee3..d5498397bf 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/ResourceIdentityRequirements.cs @@ -5,7 +5,7 @@ namespace JsonApiDotNetCore.Serialization.Request.Adapters; /// -/// Defines requirements to validate an instance against. +/// Defines requirements to validate a instance against. /// [PublicAPI] public sealed class ResourceIdentityRequirements