JSON Pointer is a syntax that allows you to isolate a single element within a JSON document by navigating down a series of object properties and array indices.
Syntax
The syntax is really simple:
1
/objects/and/3/arrays
This pointer has four segments. Each segment specifies either an object property or, if the segment is a number, an array index. Interestingly, the 3
above could be either an object property or an array index. There’s nothing about the pointer that specifies a distinction. It will resolve for both of these documents:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"objects": {
"and": [
"item zero",
null,
2,
{
"arrays": "found me"
}
]
}
}
{
"objects": {
"and": {
"3": {
"arrays": "found me"
}
}
}
}
If a property contains a /
, it must be escaped by replacing it with ~1
. Additionally, the escape character ~
must be escaped by replacing it with ~0
.
It also supports a URL format, which is essentially the same thing, except that it starts with a #
, then followed by the standard pointer. This format also will %
-encode any URL-reserved characters, like =
and ?
.
In code
The JsonPointer
class is the model for JSON Pointer.
There are three ways create pointers:
- parsing with either
Parse()
orTryParse()
1
var pointer = JsonPointer.Parse("/objects/and/3/arrays");
- building with
Create()
and supplying the segments explicitly1
var pointer = JsonPointer.Create("object", "and", 3, "arrays");
- building with
Create<T>()
and supplying a LINQ expression (also see below)1
var pointer = JsonPointer.Create<MyObject>(x => x.objects.and[3].arrays);
All of these options will give you an instance of the model that can be used to evaluate JSON data.
1
2
3
using var element = JsonDocument.Parse("{\"objects\":{\"and\":[\"item zero\",null,2,{\"arrays\":\"found me\"}]}}");
var result = pointer.Evaluate(element);
// result contains a JsonElement with a "found me" value
or
1
2
3
4
var element = JsonNode.Parse("{\"objects\":{\"and\":[\"item zero\",null,2,{\"arrays\":\"found me\"}]}}");
var success = pointer.TryEvaluate(element, out var result);
// success is true
// result contains a JsonNode with a "found me" value
The designers of the
JsonNode
API have elected (for reasons I disagree with) to consider JSON null and .Net null to be equivalent. This goes against both my personal experience building Manatee.Json and theJsonElement
API, both of which maintain a separation between these two null concepts. Because of the unified design, it’s impossible to determine whether a returnedJsonNode
value ofnull
represents a value that is present but null or it is merely absent from the data. To accommodate this, the evaluation method can only support the familiarTryParse()
signature. A return oftrue
indicates the value was found, andfalse
indicates it was not. In the case of atrue
return,result
may still be null, indicating the value was found and was a JSON null.
Building pointers using Linq expressions
When building a pointer using the Create<T>()
method which takes a Linq expression, there are a couple of things to be aware of.
First, JSON Pointer supports using -
as a segment to indicate the index beyond the last item in an array. This has several use cases including creating a JSON Patch to add items to arrays.
Secondly, you have some name transformation options at your disposal.
The first way to customize your pointer is by using the [JsonPropertyName]
attribute to provide a custom name. Since this attribute controls how System.Text.Json serializes the property, this attribute will override any other options.
The second way to customize your pointer is by providing a PointerCreationOptions
object as the second parameter. Currently there is only the single option: PropertyNamingResolver
. This property is a function that takes a MemberInfo
and returns the string to use in the pointer. Several presets have been created for you and are available in the PropertyNamingResolvers
static class:
Name | Summary |
---|---|
AsDeclared | Makes no changes. Properties are generated with the name of the property in code. |
CamelCase | Property names to camel case (e.g. camelCase ). |
KebabCase | Property names to kebab case (e.g. Kebab-Case ). |
PascalCase | Property names to pascal case (e.g. PascalCase ). |
SnakeCase | Property names to snake case (e.g. Snake_Case ). |
UpperKebabCase | Property names to upper kebab case (e.g. UPPER-KEBAB-CASE ). |
UpperSnakeCase | Property names to upper snake case (e.g. UPPER_SNAKE_CASE ). |
Relative JSON Pointers
JSON Hyperschema relies on a variation of JSON Pointers called Relative JSON Pointers that also includes the number of parent and/or array-index navigations. This allows the system to start at an internal node in the JSON document and navigate to another node potentially on another subtree.
Relative JSON Pointers are implemented with the RelativeJsonPointer
struct. Interactions with this struct are very similar to JsonPointer
.
Since evaluation of these pointers require parent navigation, a feature which is unsupported by JsonElement
s, only the JsonNode
s can be processed.
Ahead of Time (AOT) compatibility
JsonPointer.Net v4 includes updates to support Native AOT applications. However because everything in this library is handled via parsing and direct-to-string output, you don’t need to do anything.
You’re done. Congratulations.