@@ -13,7 +13,7 @@ namespace JsonApiDotNetCore.Serialization.JsonConverters;
13
13
/// Converts <see cref="ResourceObject" /> to/from JSON.
14
14
/// </summary>
15
15
[ UsedImplicitly ( ImplicitUseKindFlags . InstantiatedNoFixedConstructorSignature ) ]
16
- public sealed class ResourceObjectConverter : JsonObjectConverter < ResourceObject >
16
+ public class ResourceObjectConverter : JsonObjectConverter < ResourceObject >
17
17
{
18
18
private static readonly JsonEncodedText TypeText = JsonEncodedText . Encode ( "type" ) ;
19
19
private static readonly JsonEncodedText IdText = JsonEncodedText . Encode ( "id" ) ;
@@ -99,7 +99,7 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver
99
99
}
100
100
case "relationships" :
101
101
{
102
- resourceObject . Relationships = ReadSubTree < IDictionary < string , RelationshipObject ? > > ( ref reader , options ) ;
102
+ resourceObject . Relationships = ReadRelationships ( ref reader , options ) ;
103
103
break ;
104
104
}
105
105
case "links" :
@@ -157,7 +157,7 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver
157
157
return null ;
158
158
}
159
159
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 )
161
161
{
162
162
var attributes = new Dictionary < string , object ? > ( ) ;
163
163
@@ -174,6 +174,18 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver
174
174
string attributeName = reader . GetString ( ) ?? string . Empty ;
175
175
reader . Read ( ) ;
176
176
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
+
177
189
AttrAttribute ? attribute = resourceType . FindAttributeByPublicName ( attributeName ) ;
178
190
PropertyInfo ? property = attribute ? . Property ;
179
191
@@ -219,6 +231,57 @@ public override ResourceObject Read(ref Utf8JsonReader reader, Type typeToConver
219
231
throw GetEndOfStreamError ( ) ;
220
232
}
221
233
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
+
222
285
/// <summary>
223
286
/// Ensures that attribute values are not wrapped in <see cref="JsonElement" />s.
224
287
/// </summary>
@@ -244,13 +307,33 @@ public override void Write(Utf8JsonWriter writer, ResourceObject value, JsonSeri
244
307
if ( ! value . Attributes . IsNullOrEmpty ( ) )
245
308
{
246
309
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 ( ) ;
248
321
}
249
322
250
323
if ( ! value . Relationships . IsNullOrEmpty ( ) )
251
324
{
252
325
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 ( ) ;
254
337
}
255
338
256
339
if ( value . Links != null && value . Links . HasValue ( ) )
@@ -267,4 +350,14 @@ public override void Write(Utf8JsonWriter writer, ResourceObject value, JsonSeri
267
350
268
351
writer . WriteEndObject ( ) ;
269
352
}
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
+ }
270
363
}
0 commit comments