From c777ba13d80ba7b7fc859a0a81ed2fdb205ad9e1 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 23 Feb 2025 14:52:33 +0100 Subject: [PATCH] Refactor internal-only JSON:API extensions support --- .../JsonConverters/ResourceObjectConverter.cs | 48 ++++++++++++------- .../ResourceObjectConverterTests.cs | 10 ++-- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/JsonConverters/ResourceObjectConverter.cs b/src/JsonApiDotNetCore/Serialization/JsonConverters/ResourceObjectConverter.cs index 369adee7a3..2f820175d2 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonConverters/ResourceObjectConverter.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonConverters/ResourceObjectConverter.cs @@ -44,9 +44,9 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver var resourceObject = new ResourceObject { - // The 'attributes' element may occur before 'type', but we need to know the resource type before we can deserialize attributes - // into their corresponding CLR types. - Type = PeekType(ref reader) + // The 'attributes' or 'relationships' element may occur before 'type', but we need to know the resource type + // before we can deserialize attributes/relationships into their corresponding CLR types. + Type = PeekType(reader) }; ResourceType? resourceType = resourceObject.Type != null ? _resourceGraph.FindResourceType(resourceObject.Type) : null; @@ -99,7 +99,15 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver } case "relationships": { - resourceObject.Relationships = ReadRelationships(ref reader, options); + if (resourceType != null) + { + resourceObject.Relationships = ReadRelationships(ref reader, options, resourceType); + } + else + { + reader.Skip(); + } + break; } case "links": @@ -127,27 +135,27 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver throw GetEndOfStreamError(); } - private static string? PeekType(ref Utf8JsonReader reader) + private static string? PeekType(Utf8JsonReader reader) { - // https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0#an-alternative-way-to-do-polymorphic-deserialization - Utf8JsonReader readerClone = reader; + // This method receives a clone of the reader (which is a struct, and there's no ref modifier on the parameter), + // so advancing here doesn't affect the reader position of the caller. - while (readerClone.Read()) + while (reader.Read()) { - if (readerClone.TokenType == JsonTokenType.PropertyName) + if (reader.TokenType == JsonTokenType.PropertyName) { - string? propertyName = readerClone.GetString(); - readerClone.Read(); + string? propertyName = reader.GetString(); + reader.Read(); switch (propertyName) { case "type": { - return readerClone.GetString(); + return reader.GetString(); } default: { - readerClone.Skip(); + reader.Skip(); break; } } @@ -181,7 +189,7 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver string extensionNamespace = attributeName[..extensionSeparatorIndex]; string extensionName = attributeName[(extensionSeparatorIndex + 1)..]; - ValidateExtensionInAttributes(extensionNamespace, extensionName, reader); + ValidateExtensionInAttributes(extensionNamespace, extensionName, resourceType, reader); reader.Skip(); continue; } @@ -232,12 +240,14 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver } // Currently exposed for internal use only, so we don't need a breaking change when adding support for multiple extensions. - private protected virtual void ValidateExtensionInAttributes(string extensionNamespace, string extensionName, Utf8JsonReader reader) + // ReSharper disable once UnusedParameter.Global + private protected virtual void ValidateExtensionInAttributes(string extensionNamespace, string extensionName, ResourceType resourceType, + Utf8JsonReader reader) { throw new JsonException($"Unsupported usage of JSON:API extension '{extensionNamespace}' in attributes."); } - private Dictionary ReadRelationships(ref Utf8JsonReader reader, JsonSerializerOptions options) + private Dictionary ReadRelationships(ref Utf8JsonReader reader, JsonSerializerOptions options, ResourceType resourceType) { var relationships = new Dictionary(); @@ -261,7 +271,7 @@ private protected virtual void ValidateExtensionInAttributes(string extensionNam string extensionNamespace = relationshipName[..extensionSeparatorIndex]; string extensionName = relationshipName[(extensionSeparatorIndex + 1)..]; - ValidateExtensionInRelationships(extensionNamespace, extensionName, reader); + ValidateExtensionInRelationships(extensionNamespace, extensionName, resourceType, reader); reader.Skip(); continue; } @@ -277,7 +287,9 @@ private protected virtual void ValidateExtensionInAttributes(string extensionNam } // Currently exposed for internal use only, so we don't need a breaking change when adding support for multiple extensions. - private protected virtual void ValidateExtensionInRelationships(string extensionNamespace, string extensionName, Utf8JsonReader reader) + // ReSharper disable once UnusedParameter.Global + private protected virtual void ValidateExtensionInRelationships(string extensionNamespace, string extensionName, ResourceType resourceType, + Utf8JsonReader reader) { throw new JsonException($"Unsupported usage of JSON:API extension '{extensionNamespace}' in relationships."); } diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Extensions/ResourceObjectConverterTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Extensions/ResourceObjectConverterTests.cs index 266874cdf9..9dbdb5d68d 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Extensions/ResourceObjectConverterTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Extensions/ResourceObjectConverterTests.cs @@ -417,7 +417,8 @@ public ExtensionAwareResourceObjectConverter(IResourceGraph resourceGraph, JsonA _requestAccessor = requestAccessor; } - private protected override void ValidateExtensionInAttributes(string extensionNamespace, string extensionName, Utf8JsonReader reader) + private protected override void ValidateExtensionInAttributes(string extensionNamespace, string extensionName, ResourceType resourceType, + Utf8JsonReader reader) { if (extensionNamespace == ExtensionNamespace && IsTypeInfoExtensionEnabled && extensionName == "fail") { @@ -429,10 +430,11 @@ private protected override void ValidateExtensionInAttributes(string extensionNa return; } - base.ValidateExtensionInAttributes(extensionNamespace, extensionName, reader); + base.ValidateExtensionInAttributes(extensionNamespace, extensionName, resourceType, reader); } - private protected override void ValidateExtensionInRelationships(string extensionNamespace, string extensionName, Utf8JsonReader reader) + private protected override void ValidateExtensionInRelationships(string extensionNamespace, string extensionName, ResourceType resourceType, + Utf8JsonReader reader) { if (extensionNamespace == ExtensionNamespace && IsTypeInfoExtensionEnabled && extensionName == "fail") { @@ -444,7 +446,7 @@ private protected override void ValidateExtensionInRelationships(string extensio return; } - base.ValidateExtensionInRelationships(extensionNamespace, extensionName, reader); + base.ValidateExtensionInRelationships(extensionNamespace, extensionName, resourceType, reader); } private protected override void WriteExtensionInAttributes(Utf8JsonWriter writer, ResourceObject value)