Storing/Accessing Extra Data¶
Many API resources have a special field called extra_data
, which is a
JSON document that can contain arbitrary data for use by extensions or
clients of the API. You are free to store data in this field, and we have
three ways of doing that:
When storing data specific to your tool/script/extension, we recommend
prefixing any top-level keys with some vendor identifier, like
vendorname-key
, or even nesting all data in a top-level vendor
key.
Some keys may not be writable or even readable. See Access Restrictions for more information.
Storing/Merging JSON Data¶
New in version 3.0.
JSON documents can be stored by using a JSON Merge Patch. This
is a simple method of specifying new JSON data to merge in. Any objects in the
data will be merged together, and anything else (arrays, strings, booleans,
etc.) will be replaced by the newly-supplied data. Anything set to null
will be removed.
To supply the data to merge in, supply the new serialized JSON document in the
extra_data:json
field, like so:
extra_data:json={
"myvendor": {
"mytool": {
"array_to_replace": [1, 2, 3],
"key_to_remove": null,
"maybe_existing_object": {
"a": 1,
"b": "test",
"c": false
}
}
}
}
In this example, we’re merging data into beanbag.mytool
(keeping any
existing data in those objects), replacing the array_to_replace
key with a
new array, removing the key_to_remove
key, and merging in some more data
into maybe_existing_object
. The objects will be created if they don’t
already exist.
Note
The example above shows the JSON data spread across multiple lines. In practice, when sending this via the API, the JSON data should ideally be condensed to a single line.
This is also sent as HTTP Form Data, like any other field being set in the API.
When merging in new JSON data, the following rules are followed:
extra_data
itself cannot be replaced (for instance, supplyingextra_data:json=[]
will fail).If a new key does not match an existing key in
extra_data
, it will be added.If a new matches an existing key in
extra_data
, and the new value isnull
, the key will be removed.If a new key matches an existing key in
extra_data
, and either the old or new value is not an object, the old value will be replaced with the new one.If a new key matches an existing key in
extra_data
, and both the old and new value is an object, the object will be merged using these rules.
While objects are merged together, arrays are not. To update existing arrays, you will want to patch extra_data instead.
Patching JSON Data¶
New in version 3.0.
extra_data
fields also support a more advanced form of modification in the
form of a JSON Patch. These patches supply a list of operations to perform,
which may consist of adding new keys, replacing old ones, inserting into
arrays, copying/moving keys, and even testing for the existence of certain
data (aborting the patch if not found).
To patch extra_data
, set a serialized JSON Patch in the
extra_data:json-patch
field. For example:
extra_data:json-patch=[
{
"op": "add",
"path": "/myvendor/mytool/new_key",
"value": "new-value"
},
{
"op": "remove",
"path": "/myvendor/mytool/key_to_remove",
},
{
"op": "replace",
"path": "/myvendor/mytool/existing_key",
"value": "new-value"
}
]
This example shows just a few of the operations, the addition of a brand-new key, the removal of an existing key, and replacing the value for an existing key. These operations all work on paths (documented below), which specify a location within a JSON document.
If any operation in a patch were to fail (due to a non-existent key, or some other conflict), no part of the watch will apply, and the API request will fail with an Invalid Form Data error.
Operations¶
Add¶
Data can be added to an object or an array through the add
operation.
It takes the form of:
{
"op": "add",
"path": "/path/to/key/or/index",
"value": "new value"
}
If the key at the path already exists, it will be replaced. Otherwise, it will be created.
The new value can be simple data like a string or number, or it can be a complete JSON document by itself.
Remove¶
Data can be removed from an object or array through the remove
operation.
It takes the form of:
{
"op": "remove",
"path": "/path/to/key/or/index",
}
If the path does not exist, the patch will fail.
Replace¶
Existing data in an array or key can be replaced with a new value through the
replace
operation. It takes the form of:
{
"op": "replace",
"path": "/path/to/key/or/index",
"value": "new value"
}
If the path does not exist, the patch will fail.
The new value can be simple data like a string or number, or it can be a complete JSON document by itself.
Copy¶
Data can be copied from one location (such as an object or array) to another
location anywhere in extra_data
through the copy
operation. It takes
the form of:
{
"op": "copy",
"from": "/path/to/source/key/or/index",
"path": "/path/to/new/key/or/index"
}
If the “from” path does not exist, or the destination path cannot be written to, the patch will fail.
Move¶
Data can be moved from one location (such as an object or array) to another
location anywhere in extra_data
through the move
operation. It takes
the form of:
{
"op": "move",
"from": "/path/to/source/key/or/index",
"path": "/path/to/new/key/or/index"
}
If the “from” path does not exist, or the destination path cannot be written to, the patch will fail.
Test¶
A patch can sanity-check that there’s some expected data already stored in
extra_data
. If that data is not present as expected, the patch will fail.
{
"op": "test",
"from": "/path/to/source/key/or/index",
"value": "expected value"
}
Specifying Paths¶
All patch operations take at least one path, using the JSON Pointer specification. These paths specify a location within a JSON document,
using /
to separate object keys and array indices.
Paths always begin with a /
.
To specify an object key, just list the key name.
To specify an index in an array, specify the 0-based index of the array. If
you want to specify the tail end of an array (for the purposes of appending to
an array), use the special -
character instead of a numeric index.
Special Escape Characters¶
If you need to reference a key with a /
or ~
in the name, you’ll need
to use a special escape character.
To specify a key containing /
, use ~1
instead.
To specify a key containing ~
, use ~0
instead.
Examples¶
Here are some examples of paths:
/myvendor/mytool/mykey
/myvendor/mytool/myarray/2
/myvendor/mytool/myarray/2/nested-key
/myvendor/mytool/myarray/-
/myvendor/mytool/-1
/myvendor/mytool/-0/nested-key
Simple Key/Value Assignment¶
If you need to store top-level data directly in extra_data
, you can use
simple assignments in the form of:
extra_data.mykey=myvalue
The value can be a string, a numeric value (integer or floating-point), or a
boolean (true
or false
, case-insensitive). Anything that doesn’t look
like a number or boolean is considered a string.
If an empty value is provided for a key, and the key already exists in
extra_data
, it will be removed.
Note
This is a legacy way of working with extra_data
, available for versions
prior to Review Board 3.0. It has limitations, like being unable to work
with nested keys, unable to store complex JSON documents, and unable to
store strings that look like numbers or booleans.
If targeting Review Board 3.0 or higher, we recommend using the more modern merging or patching methods instead.
Access Restrictions¶
There are certain keys that, if present in extra_data
, will not be shown
to clients and cannot be modified.
First, any key starting with double underscores (__
) is considered private
for internal use by Review Board or extensions
only. API clients cannot see these keys or anything underneath them.
Extensions may also mark some keys as read-only or private by making use of APIExtraDataAccessHook. This allows extensions (while enabled) to limit what API clients are able to do.
Finally, some API resources may impose their own limitations on certain keys.