JSON Mapping defines a declarative method for specifying transformations of JSON data structures using JSON Pointers, JSON Schemas, and JSON References. JSON Mappings are useful where a service may wish to present multiple JSON representations of its data and for clients that consume similar data from multiple sources and wish to normalize heterogeneous input structures for processing or storage.
JSON Mapping operations can rename properties, select subsets of an object, destructure and restructure complex nested objects, coerce types using JSON Schema Type Coercion Rules [to be drafted], assign default values using the JSON Schema “default” keyword, and incorporate referenced values declared as JSON References.
A JSON Mapping object is a JSON object or array in which keys (in the case of an object) and values contain JSON Pointer strings representing paths to read from a source object and write to a target object. The value of a JSON Mapping can be a JSON Pointer string or an object with properties that declare the method for deriving an appropriate value to map.
JSON Mappings can be expressed in JSON as objects or arrays. In JSON Mapping objects, keys are strings representing JSON Pointers and values can be either JSON Pointer strings or descriptive objects containing one or more properties representing specified keywords, "pointer", "type", "default", or "$ref". String and object values can be used in the same mapping.
{
"/a": "/a/0",
"/b": {
"pointer": "/b",
"type: "string",
"default": "I am a value"
// additional or alternative keywords
},
"/c": {
"$ref": "https://example.com/data.json"
}
}
In a mapping such that all keys-value pairs in the mapping have equality between key and value, the mapping can be expressed more succinctly as an array of JSON Pointers. The following two mappings are equivalent:
{
"/a": "/a",
"/c/d": "/c/d",
"/e/2": "/e/2"
}
[ "/a", "/c/d", "/e/2" ]
Implementations MUST ignore members of the mapping that are not JSON Pointers or defined as valid property names in this document.
$id $uri $schemas
pointer
The `pointer` property MUST be present for any descriptor that does not contain the `$ref` property. It
MUST NOT be present when the `$ref`. When present it MUST be a valid JSON Pointer string.
type
The `type` property describes the expected type to be assigned. Implementations MUST attempt to coerce
types written to target objects according to JSON Schema Type Coercion Rules. When types cannot be coerced
the original value MUST be assigned to the target and error conditions SHOULD be ignored. Use of this property
is OPTIONAL.
default
The `default` property specifies the value to be assigned to a target object in the event a corresponding value is
not present in the source object at the JSON Pointer defined in the descriptor. Implementations MUST respect this
property. In the event a `type` is defined in the same descriptor, default values MAY be of a different type.
Implementations MUST NOT attempt to coerce types. Use of this property is OPTIONAL.
$ref
There are two primary operations that can be performed using a JSON Mapping: “map” and “project”. A third operation, "select", is a specialization of "map". Operations read from a source document and write to a target document, according to a mapping. The target can represent an empty object, an empty array, an object with existing properties, or an array with existing elements. In case the source and target object are equivalent, operations MUST leave existing properties intact while performing transformations described by the mapping. Operations MUST ignore error conditions that arise during JSON Pointer-based read and write operations.
Keys in a mapping object that are not JSON Pointers MUST be ignored by operations over the mapping.
The map operation iterates over key-value pairs in a mapping and reads values from a source object using the “right side” of a mapping (its value) and assigns those values to a target object using the corresponding “left side” of the mapping (its key).
{
“/a”: “/b/0”,
“/b”: “/b/1/bar”,
“/c/d”: “/c/def”
}
{
b: [
{ “foo”: true },
{ “bar”: false }
],
c: {
“def”: 1337
},
q: 3
}
{
a: {
“foo”: true
},
b: false,
c: {
d: 1337
}
}
The "project" operation reverses "map". It iterates over key-value pairs in a mapping and reads values from a source object using the “left side” of a mapping (its key) and assigns those values to a target object using the corresponding “right side” of the mapping (its value).
{
“/a”: “/b/0”,
“/b”: “/b/1/bar”,
“/c/d”: “/c/def”
}
{
a: {
“foo”: true
},
b: false,
c: {
d: 1337
}
}
{
b: [
{ “foo”: true },
{ “bar”: false }
],
c: { “def”: 1337 }
}
A mapping is a “selection” if all of its key-value pairs have equal keys and values. Such mappings can be abbreviated as an array of pointers. The “select” operation is equivalent to the map operation, except that it takes an array of JSON Pointers instead of a mapping object.
{
“/a”: “/a”,
“/d/f”: “/d/f”,
}
[ “/a”, “/d/f” ]
{
"a": 1,
"b": 2,
"c": 3,
"d": {
"e": 4,
"f": 5
}
}
{
"a": 1,
"d": {
"f": 5
}
}
Default behavior for operations is to only write values to the source object from the target object which are explicitly specified by the mapping. This default behavior can be thought of as "selection" or "filtering".
- filtering (default behavior)
- allowing additional properties (like a "deep copy" with partial transformations)
- difference between additional properties and mapping from source to itself as the target
The "type" keyword indicates the expected type of value to be assigned to a given property on a target. Mappings that include this keyword MUST attempt to coerce corresponding values from the source object to the expected type according to JSON Schema Type Coercion Rules [to be drafted]. In the event a value is present and cannot be coerced to the expected type, it should be assigned as is without creating an error condition.
Mappings MAY use the "default" keyword to indicate a value that should be assigned for a given pointer in the absence of a corresponding value on the source object. Implementations MUST respect default value declarations.
Data external to the source document can be incorporated into the result of a mapping operation by resolving a JSON Reference on the right side of a mapping.
Mappings can use Regular Expressions in a variety of ways.
- include property from source if pointer matches a pattern?
- limited to source side of mapping?
- include a value if matches a regular expression
- use a regular expression match from the read value as the write value
- pointer expressions
- reference expressions
- value expressions
- math operators
- math functions?
- string operators
- string templates
- date format transforms
Additional non-mapping properties can be included in a mapping document.
Any property name that is not a JSON Pointer is ignored.
JSON Mappings implicitly represent a pair of schemas. Mappings should therefore hypothetically be validatable against a pair of JSON Schemas for the respective source and target objects.
Because JSON Mappings are expressed as JSON, they can be transmitted between parties. Users should take care not to disclose sensitive information about data contained on any system.
- [IETF] JavaScript Object Notation
- [IETF] JSON Schema
- [IETF] JSON Pointer
- [IETF] JSON Reference
- [TBD] JSON Schema Type Coercion Rules
- [TBD] JSON Schema-based Object Initialization
{ “/changed”: “/original” }
{ "original": "value" }
{ "changed": "value" }
{
"/a": "/a",
"/c/e": "/c/e",
"/g": "/g"
}
Since each key-value pair has equality of keys and values, we can express the mapping as a selection.
["/a", "/c/e", "/g"]
{
"a": 1,
"b": 2,
"c": {
"d": 3,
"e": 4,
"f": 5
},
"g": 6,
"h": 7
}
{
"a": 1,
"c": {
"e": 4
},
"g": 6
}
{
“/a/b/c/d”: “/c/d”,
“/a/b/c/e”: “/g”,
"/f": "/c/f",
"/g/h/0": "/c/e"
}
{
"a": 1,
"b": 2,
"c": {
"d": 3,
"e": 4,
"f": 5
},
"g": 6,
"h": 7
}
{
"a": {
"b": {
"c": {
"d": 3,
"e": 6
}
}
},
"f": 5,
"g": {
"h": [4]
}
}
{
“/coerced”: {
"pointer": “/original”,
"type": "number"
}
}
{ "original": "1337" }
{ "coerced": 1337 }
{
“/a”: “/g”,
“/d/f”: {
"pointer": "/d/f",
"default": 6
},
}
{
"a": 1,
"b": 2,
"c": 3,
"d": {
"e": 4,
"f": 5
}
}
{
"d": {
"f": 6
}
}
{
“/foo”: “/foo + /bar”
}
{
“/a/b”: “/c”,
“/d/e”: {
“pointer”: “/f/g/3”,
“type”: “boolean”,
“default”: false
},
“h”: { “$ref”: “https://..." },
“I”: {
pointer: “/f/%r/%r”,
patterns: [
/^foo.*/I,
/^bar.*/
]
match: { …} // JSON Schema
}
}
query = {
select: [“/a”, “/b/c”],
match: {
// json schema
}
}
{
‘/a/c’, ‘/e’,
‘/a/b’: {
pointer: ‘/c/d/3’,
schema: { type: ‘boolean’, default: false }
},
‘/a/f/0’: {
$ref: ‘https://...’
}
}