Skip to content

Commit

Permalink
Create proposal for JSON Schema $ref
Browse files Browse the repository at this point in the history
  • Loading branch information
drwpow committed Jan 11, 2025
1 parent da1245f commit 113aa83
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 28 deletions.
122 changes: 108 additions & 14 deletions technical-reports/format/aliases.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,130 @@ Instead of having explicit values, tokens can reference the value of another tok
Aliases are useful for:

- Expressing design choices
- Eliminating repetition of values in token files (DRYing up the code)
- Eliminating repetition of values in token files (<abbr title="Don’t Repeat Yourself">DRY</abbr>ing up the code)

For a design token to reference another, its value MUST be a string containing the period-separated (`.`) path to the token it's referencing enclosed in curly brackets.
At any part of the schema, you may refer to another part of the schema with a reference. This follows the usage of `$ref` from the [JSON Schema 2020-12 specification](https://json-schema.org/draft/2020-12/json-schema-core).

For example:
### Referencing tokens

You may reference values in the same file by starting with the fragment character (`#`) followed by the token path, all separated by forward slashes:

<aside class="example">

```json
{
"color": {
"blue": {
"4": { "$value": "#218bff", "$type": "color" }
},
"brand": {
"$description": "Brand color",
"$ref": "#/color/blue/4"
}
}
}
```

Would resolve to:

```json
{
"color": {
"blue": {
"4": { "$value": "#218bff", "$type": "color" }
},
"brand": {
"$description": "Brand color",
"$value": "#218bff",
"$type": "color"
}
}
}
```

</aside>

In other words, `$ref` will act as if it’s importing the object it’s pointing to, with any sibling values being kept.

Tooling MUST allow `$ref` to have sibling values that act as overrides. In case of a conflict, tooling MUST keep the sibling values to `$ref` (in other words, `$ref` is resolved first, then any sibling keys are applied).

<p class="ednote" title="JSON Schema 2020-12">
JSON Schema 2020-12 was chosen over 2019-09 because it allows sibling overrides alongside `$ref`.
</p>

### Referencing non-tokens

It’s not just tokens that may be referenced; any part of the document may be referenced. For example, groups:

<aside class="example">

```json
{
"group name": {
"token name": {
"$value": 1234,
"$type": "number"
"color": {
"gray": {
"1": { "$value": "#101010", "$type": "color" },
"2": { "$value": "#202020", "$type": "color" },
"3": { "$value": "#404040", "$type": "color" },
"4": { "$value": "#808080", "$type": "color" }
},
"grey": { "$ref": "#/color/gray" }
}
}
```

Would resolve to:

```json
{
"color": {
"gray": {
"1": { "$value": "#101010", "$type": "color" },
"2": { "$value": "#202020", "$type": "color" },
"3": { "$value": "#404040", "$type": "color" },
"4": { "$value": "#808080", "$type": "color" }
},
"grey": {
"1": { "$value": "#101010", "$type": "color" },
"2": { "$value": "#202020", "$type": "color" },
"3": { "$value": "#404040", "$type": "color" },
"4": { "$value": "#808080", "$type": "color" }
}
}
}
```

</aside>

Any group or property (`$type`, `$value`, `$description`, `$extension`, etc.) may all be referenced, so long as the final resolved value results in a valid schema.

### Remote files

Referencing remote files may be done by using a valid URL for `$ref`. This may be a relative URL or an absolute URL. To optionally reference a sub-schema, use the fragment character `#` at the end of the URL, and [complete the reference as you would normally](#referencing-tokens).

<aside class="example">

```json
{
"space": {
"sm": {
"$description": { "$ref": "./shared.json#/space/sm/$description" },
"$value": { "$ref": "./shared.json#/space/sm/$value" },
"$type": { "$ref": "./shared.json#/space/sm/$type" }
}
},
"alias name": {
"$value": "{group name.token name}"
"typography": {
"$ref": "https://example.com/api/v1/tokens/typography.json"
}
}
```

</aside>
If there is no `#` character, the entire file will be loaded. So this means files MAY contain partial or incomplete schemas. The same rules apply—it MUST resolve to a valid, complete schema in the end.

When a tool needs the actual value of a token it MUST resolve the reference - i.e. lookup the token being referenced and fetch its value. In the above example, the "alias name" token's value would resolve to 1234 because it references the token whose path is `{group name.token name}` which has the value 1234.
## Additional info

When a tool needs the actual value of a token it MUST resolve all aliases and references - i.e. lookup the token being referenced and fetch its value.

Tools SHOULD preserve references and therefore only resolve them whenever the actual value needs to be retrieved. For instance, in a [=design tool=], changes to the value of a token being referenced by aliases SHOULD be reflected wherever those aliases are being used.

Aliases MAY reference other aliases. In this case, tools MUST follow each reference until they find a token with an explicit value. Circular references are not allowed. If a design token file contains circular references, then the value of all tokens in that chain is unknown and an appropriate error or warning message SHOULD be displayed to the user.

<p class="ednote" title="JSON Pointer syntax">
The format editors are currently researching JSON Pointer syntax to inform the exact syntax for aliases in tokens. <a href="https://datatracker.ietf.org/doc/html/rfc6901#section-5">https://datatracker.ietf.org/doc/html/rfc6901#section-5</a>
</p>
29 changes: 16 additions & 13 deletions technical-reports/format/composite-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Every shadow style has the exact same parts (color, X & Y offsets, etc.), but th
Specifically, a composite type has the following characteristics:

- Its value is an object or array, potentially containing nested objects or arrays, following a pre-defined structure where the properties of the (nested) object(s) or the elements of the (nested) arrays are sub-values.
- Sub-values may be explicit values (e.g. `"#ff0000"`) or references to other design tokens that have sub-value's type (e.g. `"{some.other.token}"`).
- Sub-values may be explicit values (e.g. `"#ff0000"`) or references to other design tokens that have sub-value's type (e.g. `{ "$ref": "#/some/other/token" }`).

A design token whose type happens to be a composite type is sometimes also called a composite (design) token. Besides their type, there is nothing special about composite tokens. They can have all the other additional properties like [`$description`](#description) or [`$extensions`](#extensions). They can also be referenced by other design tokens.

Expand Down Expand Up @@ -53,9 +53,9 @@ A design token whose type happens to be a composite type is sometimes also calle
"$type": "shadow",
"$description": "A composite token where some sub-values are references to tokens that have the correct type and others are explicit values",
"$value": {
"color": "{color.shadow-050}",
"offsetX": "{space.small}",
"offsetY": "{space.small}",
"color": { "$ref": "#/color/shadow-050" },
"offsetX": { "$ref": "#/space/small" },
"offsetY": { "$ref": "#/space/small" },
"blur": { "value": 1.5, "unit": "rem" },
"spread": { "value": 0, "unit": "rem" }
}
Expand All @@ -65,8 +65,8 @@ A design token whose type happens to be a composite type is sometimes also calle
"component": {
"card": {
"box-shadow": {
"$description": "This token is an alias for the composite token {shadow.medium}",
"$value": "{shadow.medium}"
"$description": "This token is an alias for the composite token #/shadow/medium",
"$value": { "$ref": "#/shadow/medium" }
}
}
}
Expand Down Expand Up @@ -160,7 +160,10 @@ Object stroke style values MUST have the following properties:
"notification-border-style": {
"$type": "strokeStyle",
"$value": {
"dashArray": ["{dash-length-medium}", { "value": 0.25, "unit": "rem" }],
"dashArray": [
{ "$ref": "#/dash-length-medium" },
{ "value": 0.25, "unit": "rem" }
],
"lineCap": "butt"
}
},
Expand Down Expand Up @@ -230,7 +233,7 @@ Represents a border style. The `$type` property MUST be set to the string `borde
"focusring": {
"$type": "border",
"$value": {
"color": "{color.focusring}",
"color": { "$ref": "#/color/focusring" },
"width": {
"value": 1,
"unit": "px"
Expand Down Expand Up @@ -439,12 +442,12 @@ Describes a gradient that is solid yellow for the first 2/3 and then fades to re
"position": 0
},
{
"color": "{brand-primary}",
"color": { "$ref": "#/brand-primary" },
"position": 0.5
},
{
"color": "#000000",
"position": "{position-end}"
"position": { "$ref": "#/position-end" }
}
]
}
Expand Down Expand Up @@ -495,9 +498,9 @@ Represents a typographic style. The `$type` property MUST be set to the string `
"microcopy": {
"$type": "typography",
"$value": {
"fontFamily": "{font.serif}",
"fontSize": "{font.size.smallest}",
"fontWeight": "{font.weight.normal}",
"fontFamily": { "$ref": "#/font/serif" },
"fontSize": { "$ref": "#/font/size/smallest" },
"fontWeight": { "$ref": "#/font/weight/normal" },
"letterSpacing": {
"value": 0,
"unit": "px"
Expand Down
2 changes: 1 addition & 1 deletion technical-reports/format/terminology.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ These definitions are focused on the technical aspects of the specification, aim

## (Design) Token

A (Design) Token is information associated with a human readable name, at minimum a name/value pair.
A (Design) Token is information associated with a human readable name, at minimum a name/value pair.

For example:

Expand Down

0 comments on commit 113aa83

Please sign in to comment.