Skip to content

Commit cbe6359

Browse files
committed
Add internal-only extensibility for validating/writing JSON:API extensions during serialization (needed for OpenAPI support)
1 parent 4816a48 commit cbe6359

File tree

2 files changed

+640
-5
lines changed

2 files changed

+640
-5
lines changed

src/JsonApiDotNetCore/Serialization/JsonConverters/ResourceObjectConverter.cs

+98-5
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)