Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -553,34 +553,14 @@ private static string FormatJsonPointer(ReadOnlySpan<string> path)

foreach (string segment in path)
{
ReadOnlySpan<char> span = segment.AsSpan();
sb.Append('/');

do
{
// Per RFC 6901 the characters '~' and '/' must be escaped.
int pos = span.IndexOfAny('~', '/');
if (pos < 0)
{
sb.Append(span);
break;
}

sb.Append(span.Slice(0, pos));
// Per RFC 6901 the characters '~' and '/' are escaped as '~0' and '~1'.
string escapedToken = segment.Replace("~", "~0").Replace("/", "~1");
Comment thread
adamsitnik marked this conversation as resolved.

if (span[pos] == '~')
{
sb.Append("~0");
}
else
{
Debug.Assert(span[pos] == '/');
sb.Append("~1");
}

span = span.Slice(pos + 1);
}
while (!span.IsEmpty);
// Per RFC 6901 section 6 the JSON Pointer is embedded in a URI fragment,
// so percent-encode any characters that are not valid in a URI fragment.
sb.Append(Uri.EscapeDataString(escapedToken));
Comment thread
eiriktsarpalis marked this conversation as resolved.
}

return sb.ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,29 @@ of the type which points to the first occurrence. */
}
""");

yield return new TestData<ClassWithPropertyNameRequiringFragmentEncoding>(
Value: new ClassWithPropertyNameRequiringFragmentEncoding { Value = new() },
ExpectedJsonSchema: """
{
"type": ["object","null"],
"properties": {
"hello%20world": {
"type": "object",
"properties": {
"Value" : {"type":"integer"},
"Next": {
"type": ["object","null"],
"properties": {
"Value" : {"type":"integer"},
"Next": {"$ref":"#/properties/hello%2520world/properties/Next"}
}
}
}
}
}
}
""");

yield return new TestData<ClassWithOptionalObjectParameter>(
Value: new(value: null),
AdditionalValues: [new(true), new(42), new(""), new(new object()), new(Array.Empty<int>())],
Expand Down Expand Up @@ -1614,6 +1637,12 @@ public class ClassWithJsonPointerEscapablePropertyNames
public PocoWithRecursiveMembers Value { get; set; }
}

public class ClassWithPropertyNameRequiringFragmentEncoding
{
[JsonPropertyName("hello%20world")]
public PocoWithRecursiveMembers Value { get; set; }
}

public class ClassWithOptionalObjectParameter(object? value = null)
{
public object? Value { get; } = value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public sealed partial class JsonSchemaExporterTests_SourceGen()
[JsonSerializable(typeof(PocoCombiningPolymorphicTypeAndDerivedTypes))]
[JsonSerializable(typeof(ClassWithComponentModelAttributes))]
[JsonSerializable(typeof(ClassWithJsonPointerEscapablePropertyNames))]
[JsonSerializable(typeof(ClassWithPropertyNameRequiringFragmentEncoding))]
[JsonSerializable(typeof(ClassWithOptionalObjectParameter))]
[JsonSerializable(typeof(ClassWithPropertiesUsingCustomConverters))]
// Collection types
Expand Down
Loading