@@ -13,7 +13,7 @@ namespace JsonApiDotNetCore.Serialization.JsonConverters;
1313/// Converts <see cref="ResourceObject" /> to/from JSON.
1414/// </summary>
1515[ UsedImplicitly ( ImplicitUseKindFlags . InstantiatedNoFixedConstructorSignature ) ]
16- public sealed class ResourceObjectConverter : JsonObjectConverter < ResourceObject >
16+ public class ResourceObjectConverter : JsonObjectConverter < ResourceObject >
1717{
1818 private static readonly JsonEncodedText TypeText = JsonEncodedText . Encode ( "type" ) ;
1919 private static readonly JsonEncodedText IdText = JsonEncodedText . Encode ( "id" ) ;
@@ -99,7 +99,7 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver
9999 }
100100 case "relationships" :
101101 {
102- resourceObject . Relationships = ReadSubTree < IDictionary < string , RelationshipObject ? > > ( ref reader , options ) ;
102+ resourceObject . Relationships = ReadRelationships ( ref reader , options ) ;
103103 break ;
104104 }
105105 case "links" :
@@ -157,7 +157,7 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver
157157 return null ;
158158 }
159159
160- private static Dictionary < string , object ? > ReadAttributes ( ref Utf8JsonReader reader , JsonSerializerOptions options , ResourceType resourceType )
160+ private Dictionary < string , object ? > ReadAttributes ( ref Utf8JsonReader reader , JsonSerializerOptions options , ResourceType resourceType )
161161 {
162162 var attributes = new Dictionary < string , object ? > ( ) ;
163163
@@ -174,6 +174,18 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver
174174 string attributeName = reader . GetString ( ) ?? string . Empty ;
175175 reader . Read ( ) ;
176176
177+ int extensionSeparatorIndex = attributeName . IndexOf ( ':' ) ;
178+
179+ if ( extensionSeparatorIndex != - 1 )
180+ {
181+ string extensionNamespace = attributeName [ ..extensionSeparatorIndex ] ;
182+ string extensionName = attributeName [ ( extensionSeparatorIndex + 1 ) ..] ;
183+
184+ ValidateExtensionInAttributes ( extensionNamespace , extensionName , reader ) ;
185+ reader . Skip ( ) ;
186+ continue ;
187+ }
188+
177189 AttrAttribute ? attribute = resourceType . FindAttributeByPublicName ( attributeName ) ;
178190 PropertyInfo ? property = attribute ? . Property ;
179191
@@ -219,6 +231,57 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver
219231 throw GetEndOfStreamError ( ) ;
220232 }
221233
234+ // Currently exposed for internal use only, so we don't need a breaking change when adding support for multiple extensions.
235+ private protected virtual void ValidateExtensionInAttributes ( string extensionNamespace , string extensionName , Utf8JsonReader reader )
236+ {
237+ throw new JsonException ( $ "Unsupported usage of JSON:API extension '{ extensionNamespace } ' in attributes.") ;
238+ }
239+
240+ private Dictionary < string , RelationshipObject ? > ReadRelationships ( ref Utf8JsonReader reader , JsonSerializerOptions options )
241+ {
242+ var relationships = new Dictionary < string , RelationshipObject ? > ( ) ;
243+
244+ while ( reader . Read ( ) )
245+ {
246+ switch ( reader . TokenType )
247+ {
248+ case JsonTokenType . EndObject :
249+ {
250+ return relationships ;
251+ }
252+ case JsonTokenType . PropertyName :
253+ {
254+ string relationshipName = reader . GetString ( ) ?? string . Empty ;
255+ reader . Read ( ) ;
256+
257+ int extensionSeparatorIndex = relationshipName . IndexOf ( ':' ) ;
258+
259+ if ( extensionSeparatorIndex != - 1 )
260+ {
261+ string extensionNamespace = relationshipName [ ..extensionSeparatorIndex ] ;
262+ string extensionName = relationshipName [ ( extensionSeparatorIndex + 1 ) ..] ;
263+
264+ ValidateExtensionInRelationships ( extensionNamespace , extensionName , reader ) ;
265+ reader . Skip ( ) ;
266+ continue ;
267+ }
268+
269+ var relationshipObject = ReadSubTree < RelationshipObject ? > ( ref reader , options ) ;
270+ relationships [ relationshipName ] = relationshipObject ;
271+ break ;
272+ }
273+ }
274+ }
275+
276+ throw GetEndOfStreamError ( ) ;
277+ }
278+
279+ // Currently exposed for internal use only, so we don't need a breaking change when adding support for multiple extensions.
280+ private protected virtual void ValidateExtensionInRelationships ( string extensionNamespace , string extensionName , Utf8JsonReader reader )
281+ {
282+ throw new JsonException ( $ "Unsupported usage of JSON:API extension '{ extensionNamespace } ' in relationships.") ;
283+ }
284+
222285 /// <summary>
223286 /// Ensures that attribute values are not wrapped in <see cref="JsonElement" />s.
224287 /// </summary>
@@ -244,13 +307,33 @@ public override void Write(Utf8JsonWriter writer, ResourceObject value, JsonSeri
244307 if ( ! value . Attributes . IsNullOrEmpty ( ) )
245308 {
246309 writer . WritePropertyName ( AttributesText ) ;
247- WriteSubTree ( writer , value . Attributes , options ) ;
310+ writer . WriteStartObject ( ) ;
311+
312+ WriteExtensionInAttributes ( writer , value ) ;
313+
314+ foreach ( ( string attributeName , object ? attributeValue ) in value . Attributes )
315+ {
316+ writer . WritePropertyName ( attributeName ) ;
317+ WriteSubTree ( writer , attributeValue , options ) ;
318+ }
319+
320+ writer . WriteEndObject ( ) ;
248321 }
249322
250323 if ( ! value . Relationships . IsNullOrEmpty ( ) )
251324 {
252325 writer . WritePropertyName ( RelationshipsText ) ;
253- WriteSubTree ( writer , value . Relationships , options ) ;
326+ writer . WriteStartObject ( ) ;
327+
328+ WriteExtensionInRelationships ( writer , value ) ;
329+
330+ foreach ( ( string relationshipName , RelationshipObject ? relationshipValue ) in value . Relationships )
331+ {
332+ writer . WritePropertyName ( relationshipName ) ;
333+ WriteSubTree ( writer , relationshipValue , options ) ;
334+ }
335+
336+ writer . WriteEndObject ( ) ;
254337 }
255338
256339 if ( value . Links != null && value . Links . HasValue ( ) )
@@ -267,4 +350,14 @@ public override void Write(Utf8JsonWriter writer, ResourceObject value, JsonSeri
267350
268351 writer . WriteEndObject ( ) ;
269352 }
353+
354+ // Currently exposed for internal use only, so we don't need a breaking change when adding support for multiple extensions.
355+ private protected virtual void WriteExtensionInAttributes ( Utf8JsonWriter writer , ResourceObject value )
356+ {
357+ }
358+
359+ // Currently exposed for internal use only, so we don't need a breaking change when adding support for multiple extensions.
360+ private protected virtual void WriteExtensionInRelationships ( Utf8JsonWriter writer , ResourceObject value )
361+ {
362+ }
270363}
0 commit comments