Network Working Group A. Newton
Internet-Draft ARIN
Intended status: Standards Track P. Cordell
Expires: November 18, 2019 Codalogic
May 17, 2019

A Language for Rules Describing JSON Content


This document describes a language for specifying and testing the expected content of JSON structures found in JSON-using protocols, software, and processes.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on November 18, 2019.

Copyright Notice

Copyright (c) 2019 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents ( in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Simplified BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Simplified BSD License.

Table of Contents

1. Introduction

This document describes JSON Content Rules (JCR), a language for specifying and testing the interchange of data in JSON format used by computer protocols and processes. The syntax of JCR is not JSON but is "JSON-like", possessing the conciseness and utility that has made JSON popular.

1.1. Requirements Language

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

2. Motivation

As a growing number of protocols use JSON, there is an increasing need to find better mechanisms to help define such protocols.

In the past, protocols often used constrained grammar strings. Such strings could be defined by example, but it was found better to use Backus-Naur Form (BNF), or variants such as ABNF. The benefit of using ABNF over examples is that the full variation of what is allowed in a protocol can be expressed in a single location. This leads to easier implementation and better interoperability.

As protocols migrate to being defined in JSON, the same need to define the valid set of JSON messages arises. It is conceivable to define the JSON-based message set by way of examples. But as with constrained grammar strings, this can be cumbersome, incomplete and easily misinterpreted, leading to the problems of implementation and interoperability mentioned earlier.

It would be theoretically possible to express the valid set of a protocol’s JSON messages using ABNF. However, ABNF is difficult to get right at the best of times, and defining an ABNF that simultaneously interwove the constraints of JSON and the constraints of the protocol into a single ABNF definition would be a task few could, or would want to, achieve. Even if it were possible, much of what was intended to describe the protocol would be obscured by the aspects describing the JSON constraints. Such an approach is likely to end up being only comprehendible by a machine and be impenetrable to humans. Therefore, arguably, such a definition would not satisfy it primary target audience.

The solution is to move up a level of abstraction. In the same way JSON is a level of abstraction above constrained grammar strings for representing protocols, a similar move up in abstraction level is needed for the mechanism used to define such protocols.

JSON Content Rules (JCR) is such a step up in abstraction. It’s relation to JSON is that of ABNF to constrained string grammars. By ‘knowing’ about JSON it can more accurately and concisely define JSON messages than other methods that are less abstracted. In the same way that abstracted languages such as Java and Python enable a programmer to work more efficiently than they can with assembler, protocol developers can work more efficiently using JCR than they can with ABNF.

That said, JCR is not the only language in this space nor the only solution, beyond ABNF, to this problem. Of the various method and languages that the authors know about, in addition to JCR, there are format translators which algorithmically convert a specification from one format to another (e.g. XML to JSON), abstraction languages such as Yang and CDDL, and at least one other JSON specific language: JSON Schema.

2.1. Format Translation

Format translation is an algorithmic approach to specifying protocol messages in multiple formats by using one format as the base specification and an algorithm for translating that format into another. One example would be the translation of XML into JSON using the BadgerFish algorithm and software.

This approach often creates difficult protocol messages in the destination format (i.e. JSON in the case of XML to JSON) which are hard to implement towards or debug. Additionally, while it maybe fashionable to have multiple formats, most software implementations of protocols work best with one format and often not well with others, if at all.

vCard and jCard are good examples of this. The original format and data model for vCard are specified in MIME. jCard is an algorithmic conversion of vCard to jCard. Consequently, writing software implementations of jCard requires software developers to have an intimate knowledge of MIME, saving them little in the way of time or effort.

2.2. Abstraction Languages

Abstraction languages are nothing new to the schema and data definition language space, with ASN.1 being a classic example of specifying a data model in a higher-level syntax and defined algorithms for multiple data formats. ASN.1 supports many data formats, such as BER, DER, and XER (XML Encoding Rules). Yang is a more modern and popular abstraction language.

These languages have their place but suffer the same issues as format translators as they require software implementors to spend valuable energy on a syntax that is not specific to the software being implemented. Additionally, abstraction languages have, in many instances, specified features in the data model that do not translate well to all data formats or may lack compelling features because the language must cater to multiple formats.

With respect to JSON, CDDL is an abstraction language as it's data model is a superset of the data model of JSON. In other words, it is possible to specify protocol messages in CDDL that are not applicable to JSON. And because CDDL targets CBOR specifically, it does not benefit from being JSON-like, as is the case of JCR, or specified in JSON, as is the case of JSON Schema.

2.3. JSON Schema vs JCR

JSON Schema, like JCR, is a data definition language designed specifically for JSON. While JCR is more tightly scoped to defining JSON protocol messages and content, JSON Schema is more broadly scoped in its goals, including deeper aspects of data set linking and the semantic web.

JSON Schema benefits from being defined in JSON (as this makes implementations of JSON Schema tools easier), but this benefit impacts readability of specifications defining content using it. To demonstrate, the following examples define JSON using the classic example of a catalog object.

In this example, the catalog entry is defined in JSON Schema.

  "$schema": "",
  "title": "Product",
  "description": "A product from Acme's catalog",
  "type": "object",
  "properties": {
    "id": {
      "description": "The unique identifier for a product",
      "type": "integer"
    "name": {
      "description": "Name of the product",
      "type": "string"
    "price": {
      "type": "number",
      "exclusiveMinimum": 0
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
    "minItems": 1,
    "uniqueItems": true
  "required": ["id", "name", "price"]

Figure 1

For comparison, this example demonstrates the same catalog entry as described in Figure 1 but in JCR.

#jcr-version 0.9
; Product – A Product for Acme’s catalog
  "id"    : integer,      ; Unique identifier for the product
  "name"  : string,       ; Name of the product
  "price" : @{exclude-min} 0.0..,
  "tags"  : [ string + ] ?

Figure 2

The above examples demonstrate that the JCR is more concise and conveys the same information but in fewer lines in a syntax familiar with a JSON-aware software implementor.

From a high-level view point, it could be said that JSON Schema is like XML Schema whereas JCR is more like Compact RelaxNG.

Additionally, JCR syntax is a superset of JSON syntax, whereby specification authors may use example JSON protocol messages as a starting point for defining JCR rules (as is described in Section 4.1). Consequently, the effort required to turn JSON examples into JCR specifications is minimal compared to that required to create an equivalent JSON Schema. This, combined with the brevity of describing rules and the ability to name rules, allows specification authors to interleave their prose with JCR rules in their specifications, facilitating describing semantics in close proximity to syntax.

3. Uses

JCR's primary focus is to help specification authors concisely and clearly describe complex JSON data structures.

Being a precise, defined format reduces the potential for misunderstanding between what specification authors intended and what software developers implement.

Being a machine-readable format, the examples in a specification can be validated by the specified JCR, and it can be verified that the JCR represents the examples. This acts like unit testing in software development and has been done in the authoring of this document. (All figures used in this document are available at [FIGS].)

JCR aids software developers to verify that their implementations conform to specifications by validating any generated JSON against the specified JCR. This can be used to highlight bugs in their code or possibly identify where a specification has omissions and requires further work.

Specific examples of JCR and JSON can be included as part of conformance test vector sets to give confidence that an implementation meets a specification. JCR's ability to be specific in some locations and loose in others allows such tests to be run on a repeatable, automated basis without requiring detailed human involvement to check the results.

JCR can help resolve interoperability issues by acting as an independent arbiter between parties experiencing interoperability issues. Appealing to a JCR opinion offers the potential for a quick and cheap resolution to a disagreement. (Naturally, either party may disagree with the JCR result and take the matter further.)

Once software has been developed and deployed, JCR offers the potential for day-one in-the-field monitoring of JSON message exchanges in order to highlight conformance issues that may have slipped through the development phase.

Being a simple, defined language, JCR can also be used on an ad-hoc basis by developers as part of the design process, such as during brainstorming and whiteboarding sessions, without risking confusion over notation that may occur if an un-documented notation is used.

4. JCR Examples

Being a superset of JSON syntax, those familiar with JSON will likely have an intuitive understanding of many aspects of JCR. This section offers some JCR examples to give such readers a feel for JCR before going into the detail.

4.1. A First Example: Specifying Content

The following JSON data describes a JSON object with two members, "line-count" and "word-count", each containing an integer.

{ "line-count" : 3426, "word-count" : 27886 }

Figure 3

This is also JCR that describes a JSON object with a member named "line-count" that is an integer that is exactly 3426 and a member named "word-count" that is an integer that is exactly 27886.

For a protocol specification, it is probably more useful to specify that each member is any integer and not specific, exact integers. Thus, a more practical JCR description would be:

{ "line-count" : integer, "word-count" : integer }

Figure 4

Since line counts and word counts should be either zero or a positive integer, the specification may be further narrowed:

{ "line-count" : 0.. , "word-count" : 0.. }

Figure 5

4.2. A Second Example: Testing Content

Building on the first example, this second example describes the same object but with the addition of another member, "file-name". An example JSON instance is:

  "file-name"  : "rfc7159.txt",
  "line-count" : 3426,
  "word-count" : 27886

Figure 6

The following JCR describes such objects:

  "file-name"  : string,
  "line-count" : 0..,
  "word-count" : 0..

Figure 7

For the purposes of writing a protocol specification, JCR may be broken down into named rules to reduce complexity and to enable re-use. The following example takes the JCR from above and rewrites the members as named rules:


$fn = "file-name"  : string
$lc = "line-count" : 0..
$wc = "word-count" : 0..

Figure 8

With each member specified as a named rule, software testers can override them locally for specific test cases. In the following example, the named rules are locally overridden for the test case where the file name is "rfc4627.txt":

$fn = "file-name"  : "rfc4627.txt"
$lc = "line-count" : 2102
$wc = "word-count" : 16714

Figure 9

This example shows how a protocol specification can describe a JSON object in general and a test environment can override the rules for testing specific cases.

4.3. A Third Example: Combining Rulesets

In addition to defining rules, which relate to individual JSON values, JCR also has directives, which have a more ruleset-wide effect.

Currently defined directives include "jcr-version", "ruleset-id", and "import". jcr-version specifies the version of JCR used by a ruleset and allows future versions of JCR. ruleset-id and import support combining rules from multiple rulesets.

Extending the previous example, it might be decided that the unsigned integer type associated with the $lc and $wc is so useful that it should be extracted into a ruleset of common utility types so that it can be used in other rulesets. Such a ruleset might look like:

#jcr-version 1.0
#ruleset-id com.example.common-types

$count = 0..

Figure 10

As this may be a long-lived ruleset, the jcr-version directive makes it clear that JCR version 1.0 is being used. The ruleset is given the identity 'com.example.common-types' using the ruleset-id directive. The rule for the type is assigned the rule name 'count'.

A ruleset that makes use of the count type may look as follows:

#import com.example.common-types as ct


$fn = "file-name"  : string
$lc = "line-count" : $ct.count
$wc = "word-count" : $ct.count

Figure 11

To make use of the count type it is first necessary to import the 'com.example.common-types' ruleset using the import directive. As part of the import, the 'com.example.common-types' ruleset is given an alias, 'ct', with which to refer to it. It is then possible to use the imported count type as '$ct.count'.

5. Overview of the Language

JCR is composed of rules (as the name suggests). A collection of rules that is processed together is a ruleset. Rulesets may also contain blank lines, comments, and directives that apply to the processing of a ruleset.

Rules are composed of two parts, an optional rule name and a rule specification. A rule specification can be either a type specification or a member specification. A member specification consists of a member name specification and a type specification.

A type specification is used to specify constraints on a superset of JSON values (e.g. number / string / object / array etc.). In addition to defining primitive types (such as string and integer), array and object types, type specifications may define the JCR specific concept of group types.

Type specifications corresponding to arrays, objects and groups may be composed of other rule specifications.

A member specification is used to specify constraints on members of a JSON object.

Rules that have a rule name may be referenced in place of rule specifications.

Rules may be defined across line boundaries and there is no line continuation syntax.

Any rule without a rule name is considered a root rule. Such a rule MUST be a type specification. Unless otherwise specified, all the root rules of a ruleset are evaluated against a JSON instance or document.

Rule specifications may be augmented with annotations to specify additional constraints and properties. For example, arrays can be augmented with an 'unordered' annotation to indicate that the order of its members isn't significant.

The syntax for each form of type specification varies depending on the type. For example:

; primitive types include literals
null         ; null literal
true         ; boolean literal
2            ; integer literal
2.0          ; float literal
"foo"        ; string literal

; primitive types include ranges
1..10        ; integer range
1.0..10.00   ; float range
/^[a-z]{4}$/ ; string 'range'

; primitive types can be unconstrained types

; primitive type rules may be named 
$my_int = 2

; member specifications consist of a member name 
; followed by a colon and followed by a
; type specification or a rule name
; (example shown with a rule name assignment)
$mem1 = "bar" : "baz" 
$mem2 = "fizz" : $my_int
; member names may either be quoted strings 
; or regular expressions
; (example shown with a rule name assignment)
$mem3 = /^dev[0-9]$/ : 0..4096
; object specifications start and end with "curly braces"
; object specifications contain zero 
; or more member specifications 
; or rule names which reference a member specification
{ $mem1, "foo" : "fuzz", "fizz" : $my_int } 
; array specifications start and end with square brackets
; array specifications contain zero 
; or more type specifications
[ 1, 2, 3, $my_int ] 

; finally, group specifications start and end with parenthesis
; groups contain member or type specifications
( [ integer, integer], $rule1 ) 
$rule1 = [ string, string ]

Figure 12

Putting it all together, the JSON in Figure 13 is described by the JCR in Figure 14.

  "Image": {
    "Width":  800,
    "Height": 600,
    "Title":  "View from 15th Floor",
    "Thumbnail": {
      "Url":    "",
      "Height": 125,
      "Width":  100
    "IDs": [116, 943, 234, 38793]

Figure 13: Example JSON shamelessly lifted from RFC 8259

; the root of the JSON instance is an object
; this root rule describes that object
  ; the object specification contains 
  ; one member specification
  "Image" : {

    ; $width and $height are defined below

    ; "Title" member specification
    "Title" :string,

    ; "Thumbnail" member specification, which 
    ; defines an object
    "Thumbnail":  { 

      ; $width and $height are re-used again
      $width, $height, 

      "Url" :uri 

    ; "IDs" member that is an array of 
    ; one ore more integers
    "IDs" : [ integer * ] 
; The definitions of the rules $width and $height
$width  = "Width" : 0..1280
$height = "Height" : 0..1024

Figure 14: JCR for JSON example from RFC 8259

In addition to defining rules, JCR also has directives. These have a more ruleset-wide effect. Simple uses of JCR will likely not use directives.

6. Language Components

This section describes each component of the JCR language in detail.

6.1. Character Encoding

Like JSON, JCR rulesets MUST be encoded using UTF-8 unless used entirely within a private, closed ecosystem.

This document assumes that both JCR rulesets and JSON instances being processed are encoded using UTF-8. Issues related to handling JCR rulesets or JSON instances that are not encoded using UTF-8 are outside the scope of this document.


Comments are the same as comments in ABNF. They start with a semi-colon (';') and continue to the end of the line.

Blank lines are allowed. These can be used, for example, to further aid readability.

6.3. Names and Identifiers

JCR uses names and identifiers to enable cross-referencing one part of a ruleset with another or from one ruleset to another. There are different types of names and different types of identifiers. For example, a local rule name is a name, and a ruleset-id is an identifier.

A name must start with an ASCII alphabetic character (a-z,A-Z) and must contain only ASCII alphabetic characters, numeric characters, the hyphen character ('-'), and the underscore character ('_'). Names are case sensitive.

An identifier must start with an ASCII alphabetic character (a-z,A-Z) and can be followed by any character other than whitespace and the closing brace ('}'). Identifiers are treated as opaque strings and therefore case-sensitive.

6.4. Directives

Directives modify the processing of a ruleset. If present, they typically appear at the start of a ruleset, before any rules are defined, but they can be placed in other parts of the ruleset if necessary. Simpler rulesets need not include any directives.

There are two forms of directive, the single line directive and the multi-line directive.

Single line directives appear on their own line in a ruleset, begin with a hash character ('#') and are terminated by the end of the line. They take the following form:

# directive_name parameter_1 parameter_2 ...

Figure 15

Multi-line directives may span multiple lines. They begin with the character sequence "#{" and end with "}". They take the following form:

#{ directive_name
    parameter_1 parameter_2

Figure 16

This specification defines the directives "jcr-version", "ruleset-id", and "import", but other directives may be defined in future.

6.4.1. jcr-version

The jcr-version directive declares that the ruleset complies with a specific version of this specification. The version follows the "jcr-version" directive name and is expressed as a major integer followed by a period followed by a minor integer.

# jcr-version 0.7

Figure 17

The major.minor number signifying compliance with this document is "0.9". Upon publication of this specification it will be "1.0". [CREF1]NOTE: This will be removed in the final version.

# jcr-version 1.0

Figure 18

This directive may have optional extension identifiers following the version number. Each extension identifiers is preceded by the plus ('+') character and separated by white space. An extension identifier has the form of an identifier as described in Section 6.3. The structure of extension identifiers is specific to the extension, but it is recommended that they are terminated by a version number.

# jcr-version 1.0 +co-constraints-1.2 +jcr-doc-1.0

Figure 19

A maximum of one jcr-version directive is permitted in a ruleset. Ruleset authors are advised to place this directive as the first line of a ruleset.

6.4.2. ruleset-id

The ruleset-id directive associates an identifier with a rulset. For example:

# ruleset-id

Figure 20

The identifier for the ruleset follows the "ruleset-id" directive name and has the form of an identifier as described in Section 6.3. The identifier can be a URL (e.g., an inverted domain name (e.g. or have any other internal structure that a ruleset author deems appropriate. To a JCR processor the identifier is treated as an opaque, case-sensitive string. An identifier that is URI based should not be taken to imply that the ruleset is accessible over the Internet at that address (although it is not prevented from being accessible in such a way).

A maximum of one ruleset-id directive is permitted in a ruleset. If present, it is suggested that it be the second line of a ruleset, following the jcr-version directive, or the first line if no jcr-version directive is present.

6.4.3. import

The import directive specifies that another ruleset is to have its rules evaluated in addition to the ruleset where the directive appears.

The following is an example:

# import as rfc9999
# import

Figure 21

The identifier after the "import" directive name is a ruleset identifier. As such, it is an identifier as described in Section 6.3. The ruleset that is imported is identified by having the specified ruleset identifier specified in its #ruleset-id directive (See Section 6.4.2). How a JCR processor locates the imported ruleset is out of scope for this document.

The ruleset identifier may optionally be followed by the 'as' keyword that is in turn followed by an alias for the identifier. An alias has the form of a name as described in Section 6.3.

The rule names of the ruleset being imported may be referenced by combining the alias followed by a period character ('.') followed by the local rule name (i.e. ""). To continue the example above, if the ruleset identified as '' were to have a rule named 'encoding', rules in the ruleset importing it can refer to it as 'rfc9999.encoding'.

If an import directive does not specify an alias with the 'as' keyword, the local names of the imported ruleset effectively become local to the importing ruleset, except that a referenced name without an alias is sought in the importing ruleset before being sought in each of the unaliased imported rulesets.

6.5. Rules

Rules have two main components, an optional rule name assignment and a rule specification.

Rules have no statement terminator and therefore no need for a line continuation syntax. Rules may be defined across line boundaries.

A rule specification can be a type specification, a member specification or a rule name reference (see Section 6.6).

Type specifications define JSON primitive types (such as boolean, numbers, and strings), arrays, and objects. In addition to the type specifications describing JSON types, there is an additional group specification for grouping specifications.

Member specifications define members of JSON objects and are composed of a member name specification followed by either a type specification or a rule name reference to a type specification.

Rule specifications may also contain annotations which may affect the meaning of all or part of the specification.

Type specifications, depending on their type, MAY contain zero or more other rule specifications. For example, an object specification might contain multiple member specifications or rule name references that resolve to other member specifications, or a mixture of member specifications and rule name references.

For the purposes of this document, rule specifications composing other specifications are called subordinate components.

Rule specifications within arrays, objects, and groups can be defined with repetitions for specifying how many instances corresponding to the specification can appear in a JSON instance.

Rules without a rule name assignment are considered root rules, though rules with a rule name assignment can be considered a root rule with the appropriate annotation (see Section 6.7).

6.6. Rule Names, Assignments, and References

Rule names are used to represent rule specifications so they can be referenced elsewhere in a ruleset. A rule name can be used in two contexts; rule name assignments and rule name references.

Rule names are signified with the dollar character ('$'), which is not part of the rule name itself. Rule names have two components, an optional ruleset identifier alias and a local rule name. If a ruleset identifier alias is present, it is separated from the local rule name by a dot character ('.'). Both ruleset identifier aliases and local rule names are types of name (see Section 6.3).

When a rule name is used in the rule name assignment context, only the local rule name part can be present. In this context, the local rule name must be unique within a ruleset (that is, no two rule name assignments may use the same local rule name in the same ruleset).

In rule name assignments, the rule name is separated from the rule specification using the '=' character.

;rule name assignments for primitive types
$fuz         = "fuz"
$some_string = string

;rule name assignment for an array
$bar = [ integer, integer, integer ]

Figure 22

In the rule name reference context, a rule name, prefixed by the dollar character ('$'), is used in place of a rule specification.

;rule name references in an object
{ "bar" : $bar, "foo" : $foo }

Figure 23

Ruleset identifier aliases may be included in a rule name reference. They enable referencing rules from another ruleset. Simple use cases of JCR will most likely not use ruleset identifiers.

In Figure 24 below, "" and "" are ruleset identifiers and "rfcXXXX" is a ruleset identifier alias. See Section 6.4.3 for details on defining and using ruleset identifier aliases.

# ruleset-id
# import as rfcXXXX
$my_encodings  = ( "mythic" | "magic" )
$all_encodings = ( $rfcXXXX.encodings | $my_encodings )

Figure 24

Rule name references within a ruleset can be used either before or after their referenced rule name assignment.

6.7. Annotations

Annotations may appear before a rule name assignment, before a type or member specification, or before a rule name reference. In each place, there may be zero or more annotations. Each annotation begins with the character sequence "@{" and ends with "}". The following is an example of a type specification with the 'not' annotation:

@{not} [ "fruits", "vegetables" ]

Figure 25

The @{not} annotation is described in Section 6.7.1.

The @{root} annotation is described in Section 6.18.

The @{unordered} annotation is described in Section 6.14.2.

The @{exclude-min} and @{exclude-max} annotations are described in Section 6.11.3.

The @{format} annotation is described in Section 6.11.6.

The @{choice} annotation is described in Section 6.9.1.

The @{augments} annotation is described in Section 6.19.

Other annotations may be defined for other purposes in future.

6.7.1. @{not} - Negating Evaluation

The evaluation of a rule can be changed with the @{not} annotation. With this annotation, a rule that would otherwise match does not, and a rule that would not have matched does.

; match anything that isn't the integer 2
$not_two = [ @{not} 2 ]
; error if one of the status values is "fail"
$status = @{not} @{unordered} [ "fail", string * ] 

Figure 26

6.8. Repetition

Evaluation of subordinate components in array, object, and group specifications may be succeeded by a repetition expression denoting how many times the subordinate component may appear in a JSON instance.

Repetition expressions are specified using a Kleene symbol ('?', '+', or '*') or with the '*' symbol succeeded by specific minimum and/or maximum values, each being non-negative integers. Repetition expressions may also be appended with a step expression, which consists of the '%' symbol followed by a positive integer.

When no repetition expression is present, both the minimum and maximum are 1.

The allowable Kleene operators are the question mark character ('?') which specifies zero or one (i.e. optional), the plus character ('+') which specifies one or more, and the asterisk character ('*') which specifies zero or more.

; age is optional
{ "name" : string, "age" : integer ? }

; zero or more errors
$error_set = ( string * )

; 1 or more integer values
[ integer + ]

Figure 27

A minimum and maximum can be expressed by immediately following the '*' symbol by the minimum followed by two period characters ('..') followed by the maximum, with either the minimum or maximum being optional. When the minimum is not explicitly specified, it is assumed to be zero. When the maximum is not explicitly specified, it is assumed to be positive infinity. While the minimum is optional, it is RECOMMENDED to include it even when it has a value of zero.

; exactly 2 octets
$word = [ $octet *2 ]
$octet = int8

; 1 to 13 name servers
[ $name_servers *1..13 ]
$name_servers = fqdn

; 0 to 99 ethernet addresses
{ /^eth.*/ : $mac_addr *..99 }
$mac_addr = hex

; four or more bytes
[ $octet *4.. ]

Figure 28

A repetition step expression may follow a minimum to maximum expression, the zero or more Kleene operator ('*'), or the one or more Kleene operator ('+').

The following is an example for repetition steps in repetition expressions:

; there must be at least 2 name servers
; there may be no more than 12 name servers
; there must be an even number of name servers
; e.g. 2,4,6,8,10,12
[ $name_servers *2..12%2 ]
$name_servers = fqdn

; minimum is zero
; maximum is 100
; must be an even number
{ /^eth.*/ : $mac_addr *..100%2 }
$mac_addr = hex

; at least 32 octets
; must be be in groups of 16
; e.g. 32, 48, 64 etc
[ $octet *32..%16 ]
$octet = int8

; if there are to be error sets,
; their number must be divisible by 4
; e.g. 0, 4, 8, 12 etc
$error_set = ( string *%4 )

; throws of a pair of dice must be divisible by 2 
; e.g. 2, 4, 6 etc
$dice_throws = ( 1..6 +%2 )

Figure 29

6.9. Combining Subordinate Components - Sequences and Choices

Combinations of subordinate components in array, object, and group specifications can be specified as either a sequence ("and") or a choice ("or"). A sequence is a subordinate component followed by the comma character (',') followed by another subordinate component. A choice is a subordinate component followed by a pipe character ('|') followed by another subordinate component.

; sequence ("and")
[ "this" , "that" ]

; choice ("or")
[ "this" | "that" ]

Figure 30

The exact meaning of a sequence or choice depends on whether it is in an object, array or group, and they are further discussed in the relevant sections.

Sequence and choice combinations cannot be mixed, and group specifications must be used to explicitly declare precedence between a sequence and a choice. Therefore, the following is illegal:

[ "this", "that" | "the_other" ]

Figure 31

The example above should be expressed as:

[ "this", ( "that" | "the_other" ) ]

Figure 32

6.9.1. @{choice} Annotation

If an object, array or group has only zero or one items, it is ambiguous whether it is intended to be a sequence or a choice. This is an issue if the object, array or group is intended to be extended in a future version of the ruleset, especially if the mechanism for extension is the @{augments} annotation (Section 6.19).

To resolve this ambiguity, an object, array or group containing only zero or one items is treated as a sequence unless it is annotated with the @{choice} annotation, in which case it is treated as a choice.

$a_sequence_extension_point = {}

$a_choice_extension_point = @{choice} {}

Figure 33

6.10. Type Specifications

Type specifications consist of primitive specifications, objects specifications, array specifications, and group specifications. Each of these are described in the sections below.

6.11. Primitive Specifications

Primitive specifications define content for JSON null, booleans, numbers, and strings.

6.11.1. Null

The null type specification is the simplest and takes the following form:


Figure 34

A value specified to be 'null' MUST be represented in a JSON instance by the JSON 'null' value.

6.11.2. Booleans

Type specifications for Booleans take the following forms:


Figure 35

A value specified to be 'true' MUST be represented in a JSON instance with the 'true' JSON value. If specified to be 'false', it MUST be represented with the 'false' JSON value. If specified to be 'boolean', it MUST be represented with either the 'true' or 'false' JSON values.

No casting or type coercion of non-Boolean types to Boolean types is permitted.

6.11.3. Numbers

Type specifications for numbers can specify a JSON instance value to be either an integer or floating point number. For example:


Figure 36

The keyword 'integer' specifies a signed integer value of arbitrary size.

The keyword 'float' represents a single precision IEEE-754 floating point number [IEEE754] represented in decimal format. The keyword 'double' represents a double precision IEEE-754 floating point number represented in decimal format.

Numbers may also be specified as specific values or a range of possible values, where a range may be specified using a minimum, maximum, or both.

To specify a specific value, the value itself is specified. Note that floating point types MUST include a fractional or exponent part, even if the fractional part is zero.

10   ; specifies the exact integer value 10
10.0 ; specifies the exact floating point value 10

Figure 37

To specify a range, the range token, consisting of two consecutive dot characters ('..'), is used. The minimum in the range, if present, is placed before the range token, and the maximum, if present, is placed after the range token (with no intervening spaces). If the minimum is absent, that specifies no lower bound. If the maximum is absent, it specifies no upper bound.


Figure 38

When specifying a minimum and a maximum, both must either be an integer or a floating point number. Thus to specify a floating point number between zero and ten a definition of the following form is used:


Figure 39

When a range is used to define a number, by default the minimum and maximum values are included in the range. The @{min-exclusive} annotation can be specified to exclude the minimum from the range, and the @{max-exclusive} annotation can be specified to exclude the maximum from the range.

$greater-than-or-equal-to-10 = 10.0..
$greater-than-10 = @{min-exclusive} 10.0..

$less-than-or-equal-to-100 = ..100.0
$less-than-100 = @{max-exclusive} ..100.0

$gt-10-lt-100 = @{min-exclusive} @{max-exclusive} 10.0..100.0

Figure 40

The range of integers may also be specified using bit lengths. The type specification is constructed with a prefix followed by a number. The prefix 'int' specifies a signed integer, and the prefix 'uint' specifies an unsigned integer. The number that follows is a positive integer that specifies the number of bits in the integer. For signed integers, the bit length is based on the two's complement integer format.

; 0..255

; -32768..32767

; 0..65535

; –9223372036854775808..9223372036854775807

; 0..18446744073709551615

Figure 41

When specifying numbers, ruleset authors are recommended to bear in mind the commentary on numbers in [RFC7493].

A value specified to be a number MUST be represented in an instance as a JSON number. Strings containing character sequences representing numbers MUST NOT be treated as numbers.

If a range is specified for a number, the JSON instance value MUST satisfy the specified range.

It is preferred that a JSON instance value specified to be an integer does NOT include a fractional part (as specified by the frac rule in the ABNF) or an exponential part (as specified by exp in the ABNF). This preferred behaviour can not be mandated because JCR defined messages are intended to be used with generic JSON generators. The following shows preferred, non-preferred and invalid representations of integer instance values:

50      ; Preferred integer format
50.0    ; Non-preferred integer format
5e1     ; Non-preferred integer format
"50"    ; Not an integer

Figure 42

6.11.4. Plain Strings

An unconstrained string may be specified using the keyword 'string'. String literals may be specified using a double quote character followed by the literal content followed by another double quote. Regular expressions can specify constrained strings by enclosing a regular expression within forward slash ('/') characters.

; any string

; a string literal
"she sells sea shells"

; a regular expression
/^she sells .*/

Figure 43

Regular expressions are not implicitly anchored and therefore must be explicitly anchored if necessary.

Regular expressions SHOULD use the ECMA 262 dialect used by JavaScript. This is mostly a sub-set of the common regular expression dialects, and any regular expression thus defined, after converting escape sequences to UTF-8, should be usable in any mainstream regular expression engine.

A value specified to be a string MUST be represented in a JSON instance as a JSON string value.

To validate a literal string specification, escape sequences in both the JCR string specification and the JSON instance value are converted to UTF-8. The two strings are then compared as opaque sequences of bytes and MUST be identical for the JSON instance value to be considered valid. They are thus treated as case-sensitive. No whitespace processing or Unicode normalization is performed. For the literal string specification "JCR Rules", both JSON instance values of "JCR Rules" and "\u004ACR Rules" are valid, while the JSON instance values "jcr rules", " JCR Rules ", or "JCR   Rules" are invalid.

To be valid against a regex string specification, a JSON instance value, after converting escape sequences to UTF-8, MUST satisfy the specified regular expression.

6.11.5. Strings with Additional Semantics

JCR provides a large number of data types beyond those defined by JSON. They are encoded in JSON instances using JSON strings.

A string can be specified to be a URI using the keyword 'uri', but also may be more narrowly scoped to a URI of a specific scheme. Specific URI schemes are specified with the keyword 'uri' followed by two period characters ('..') followed by the URI scheme.

; any URI

;a URI narrowed for an HTTPS uri

Figure 44

IP addresses may be specified with either the keyword 'ipv4' for IPv4 addresses or the keyword 'ipv6' for IPv6 addresses. Fully qualified A-label and U-label domain names may be specified with the keywords 'fqdn' and 'idn'.

Dates and times can be specified as formats found in RFC 3339. The keyword 'date' corresponds to the full-date ABNF rule, the keyword 'time' corresponds to the full-time ABNF rule, and the keyword 'datetime' corresponds to the date-time ABNF rule.

Email addresses formatted according to RFC 5322 may be specified using the 'email' keyword, and E.123 phone numbers may be specified using the keyword 'phone'.

;IP addresses

;domain names

; RFC 3339 full-date
; RFC 3339 full-time
; RFC 3339 date-time

; RFC 5322 email address

; phone number

Figure 45

Binary data can be specified in string form using the encodings specified in RFC 4648. The keyword 'hex' corresponds to base16, while 'base32', 'base32hex', 'base64', and 'base64url' correspond with their RFC 4648 counterparts accordingly.

; RFC 4648 base16

; RFC 4648 base32

; RFC 4648 base32hex

; RFC 4648 base64

; RFC 4648 base64url

Figure 46

A value specified to be a semantic string MUST be represented in a JSON instance as a JSON string value and MUST satisfy all the constraints specified by the relevant standard(s) for the semantic type.

6.11.6. Strings with External Specifications

New types will likely be added as a form of JSON string. Some such formats may have structure that can't easily be represented using regular expressions and other such techniques.

To address this a @{format} annotation is used. This annotation contains a uri identifying the format of the string being defined.

For example, a fictional JCR ruleset specifying a DNA fingerprint type might be:

# ruleset-id

$fingerprint =
    @{format} string

Figure 47

From a validators perspective, the above type could be considered to be a 'string with the format specified by'.

The method used to define the structure of the string is beyond the scope of this document. It could use regular expressions, ABNF, narrative text or some other method.

The uri is an abstract resource identifier. Whether it is resolvable to a descriptive document or not is beyond the scope of this document.

Whether validators give errors, warnings or act silently when they encounter a spec uri they don't know is an implementation detail.

6.12. Member Specifications

Member specifications define members of JSON objects. Unlike type specifications, member specifications cannot be root rules and must be part of an object specification, a group specification, or preceded by a rule name assignment.

Member specifications consist of a member name specification followed by optional whitespace followed by a colon character (':') followed by optional whitespace followed by either a rule name reference or a type specification. Member name specifications can be given either as a quoted string delimited by double quotes or as a regular expression delimited by forward slash ('/') characters. Regular expressions are not implicitly anchored and therefore must have explicit anchors if needed.

;member name will exactly match "locationURI"
$location_uri = "locationURI" : uri

;member name will match "eth0", "eth1", ... "eth9"
$iface_mappings = /^eth[0-9]$/ : ipv4

Figure 48

Member specification validation takes place as part of object validation, which is described in Section 6.13.

6.13. Object Specifications

Object specifications define the permitted contents of JSON objects and are composed of zero or more subordinate components, each of which can be either a rule name reference, member specification, or group specification. The subordinate components are enclosed at the start with a left curly brace character ('{') and at the end with a right curly brace character ('}').

Subordinate components MAY have repetitions as described in Section 6.8 and MAY be combined into sequences and/or choices as described in Section 6.9.

The following examples illustrate matching of JSON object instances to JCR object specifications.

The order of member specifications in a object specification does not dictate the order of member instances in a JSON object instance. The following object specification will match the object instances in both Figure 50 and Figure 51.

{ "locationUri" : uri, "statusCode" : integer }

Figure 49

{ "locationUri" : "", "statusCode" : 200 }

Figure 50

{ "statusCode" : 200, "locationUri" : "" }

Figure 51

6.13.1. Object Member Association

Before a JSON instance can be validated, it is necessary to associate JSON object instance members with JCR member specifications. This is performed as follows:

There are 3 types of member name specification: q-string specs, (non-empty) regex specs, and the (empty regex) wildcard spec. (e.g. "foo", /^foo/ and //.)

All name specifications within an object specification are divided into these three groups (q-string spec, regex spec, and wildcard spec). (Note that the same name specification may appear multiple times within an object specification, typically in different groups.)

Each instance member name is first tested for a match against the q-string spec name specifications. If the instance name is matched, the instance name and its value is associated with every occurrence of that name specification and its type(s).

If an instance name does not match a q-string spec name specifications, then it is tested for a match against the regex spec name specifications. If the instance name matches a single regex spec, the instance name and its value is associated with every occurrence of that name specification and its type(s). If the instance name matches more than one regex spec, the JSON instance is considered to be invalid.

If an instance name does not match either a q-string spec or regex spec name specification and a wildcard spec name specification is present, then the instance name and its value is associated with every occurrence of the wildcard specification and its type(s).

If an instance name does not match either a q-string spec or regex spec name specification and there is no wildcard spec name specification present, then the instance name and its value is ignored.

The above rules mean that any instance name can only be associated with one name specification (although multiple instances of that name specification may appear with an object specification). As a result, both of the object specifications below are equivalent:

$o1 = { /^p\d+$/ : integer *, "p1" : string }

$o2 = { "p1" : string, /^p\d+$/ : integer * }

Figure 52

The following JSON instance is valid against both of the above object specifications:

{ "p0" : 1, "p1" : "a string" }

Figure 53

6.13.2. Object Validation

It is important to be able to determine whether a JSON instance is valid against a JCR specification irrespective of whether it is a human or machine performing the assessment.

Validation of object instances against object specifications takes place as follows:

6.13.3. Closing Objects to Extension

As stated above, members of instance objects that are not associated with a member name specification are ignored. The reason for this validation model is due to the nature of the typical access model to JSON objects in many programming languages, where members of the object are obtained by referencing the member name. Therefore extra members may exist without harm.

However, some specifications may need to restrict the members of a JSON object to a known set. To construct a specification specifying that no extra members are allowed, the @{not} annotation (see Section 6.7.1) may be used with a "match-all" regular expression as the last subordinate component of the object specification.

The following rule will match the JSON object in Figure 55 but will not match the JSON object in Figure 56.

{ "foo" : 1, "bar" : 2, // : any *0 }

Figure 54

{ "foo" : 1, "bar" : 2 }

Figure 55

{ "foo" : 1, "bar" : 2, "baz" : 3 }

Figure 56

This works because the last specification is associated with any member instances that are not associated with a prior specification. As such, no member instance is ignored. But, due to the *0 repetition specification, the object fails to validate if one or more of those instances are present.

6.13.4. Object Mixins

Object rule name references can be used to create object mixins, a pattern for writing data models similar in style to object derivation in some programming languages. In the example below, both obj1 and obj2 have members "foo" and "fob" with obj1 having the additional member "bar" and obj2 having the additional member "baz".

$mixin_object = { "foo" : integer, "fob" : uri }

$obj1 = { $mixin_object, "bar" : string }

$obj2 = { $mixin_object, "baz" : string }

Figure 57

Group rules may also be used to create object mixins (See Section 6.17.2.)

6.14. Array Specifications

Array specifications define JSON arrays and are composed of zero or more subordinate components, each of which can either be a rule name or a primitive, array, object or group specification. The subordinate components are enclosed at the start with a left square brace character ('[') and at the end with a right square brace character (']').

As with object specifications, array subordinate components MAY have repetitions as described in Section 6.8 and MAY be combined into sequences and/or choices as described in Section 6.9.

Arrays can be specified as either ordered or unordered. Arrays are ordered by default.

6.14.1. Ordered Array Specifications

In an ordered array specification, unlike object specifications, the order of type specifications dictates the permitted order of array instance values.

Evaluation of the subordinate components of ordered array specifications is as follows:

Take for example the following ruleset:

; the first element of the array is to be a string
; the second element of the array is to be an integer
$a1 = [ string, integer ]

; the first element of the array is to be an integer
; the second element of the array is to be a string
$a2 = [ integer, string ]

Figure 58

It defines two rules, a1 and a2. The array in the following JSON will not match a1, but will match a2.

[ 24, "Bob Smurd" ]

Figure 59

If an array instance has more elements than can be matched from the array specification, the array instance does not validate against the array specification. Or stated differently, an array with unmatched elements does not validate. Using the example array rule a2 from above, the following array does not match because the last element of the array does not match any subordinate component:

[ 24, "Bob Smurd", "" ]

Figure 60

To allow an array to contain any value after guaranteeing that it contains the necessary items, the last subordinate component of the array specification should accept any item:

; the first element of the array is to be an integer
; the second element of the array is to be a string
; anything else can follow
$a3 = [ integer, string, any * ]

The JSON array in Figure 60 will validate against the a3 rule in this example.

Figure 61

Validating an ordered array, in the general case, has similarities with matching a regular expression or an ABNF grammar. A regular expression specifies a pattern of tokens that happen to be textual characters, whereas an ordered array specification specifies a pattern of tokens that happen to be JSON values. For example, the following JCR specification:

[ $first_name, $middle_name ?, $last_name, $birth_year ]
$first_name = string
$middle_name = string
$last_name = string
$birth_year = integer

Figure 62

should accept the JSON instance:

[ "George", "Washington", 1732 ]

Figure 63

When validating the above instance, a validator may initially associate "George" with $first_name and "Washington" with $middle_name. It will then attempt to validate 1732 against $last_name, which will fail. At this point, recognizing that $middle_name is optional, the validator must back-track and associate "Washington" with $last_name. From there it can validate 1732 with $birth_year and yield that the instance is valid.

Similarly, the following JCR:

[ string, ( string | integer ) ?, string ]

Figure 64

permits each of the following JSON instances:

[ "A", "B", "C" ]
[ "A", 1, "C" ]
[ "A", "C" ]

Figure 65

6.14.2. Unordered Array Specifications

Array specifications can be made to behave in a similar fashion to object specifications with regard to the order of matching with the @{unordered} annotation.

In the ruleset below, a1 and a2 have the same subordinate components given in the same order. a2 is annotated with the @{unordered} annotation.

$a1 =              [ string, integer ]
$a2 = @{unordered} [ string, integer ]

Figure 66

The JSON array below does not match a1 but does match a2.

[ 24, "Bob Smurd" ]

Figure 67

The @{unordered} annotation can only be applied to an array as a whole. It can not be applied to groups within an array.

Like ordered array specifications, the subordinate components in an unordered array specification are evaluated in the order they are specified. The difference is that they need not match an element of the array in the same position as given in the array specification.

Finally, like ordered array specifications, unordered array specifications also require that all elements of the array be matched by a subordinate component. If the array has more elements than can be matched, the array does not match the array specification.

6.15. Type Choices

A type specification can be a type choice. A type choice begins with an opening parenthesis ('(') and ends with a closing parenthesis (')'). Its subordinate components are type specifications or rule name references combined using the choice combiner as described in Section 6.9.

{ "age" : (0.. | "unknown") }

Figure 68

Type choices can also be used for enumerations.

{ "status" : ("open" | "closed" | "unknown" | string) }
; string included to allow for future extension

Figure 69

To validate against a type choice, an instance value MUST validate against one (or more) of the subordinate component type specifications.

6.16. Any Type

The 'any' type specifies that a JSON value instance can be any primitive type, array type, or object type.

6.17. Group Specifications

Unlike the other type specifications, group specifications have no direct tie with JSON syntax. Group specifications simply group together their subordinate components. Group specifications enclose one or more subordinate components with the parenthesis characters ('(') & (')').

Group specifications and any nesting of group specifications, must conform to the allowable set of type specifications of the type specifications in which they are referenced. For example, a group specification referenced inside of an array specification may not contain a member specification since member specifications are not allowed as direct subordinate components of array specifications (arrays contain values, not object members in JSON). Likewise, a group specification referenced inside an object specification must only contain member specifications (JSON objects may only contain object members). A group specification may also represent a type choice (See Section 6.15).

As with object and array specifications, group subordinate components MAY have repetitions as described in Section 6.8 and MAY be combined into sequences and/or choices as described in Section 6.9.

The following is an example of two group specifications that are referenced by an array specification:

$parents = ( "Mike", "Carol" )

$children = ( "Greg", "Marsha", "Bobby", "Jan" )

$the_bradys = [ $parents, $children ]

Figure 70

Group specifications are not validated against JSON instances by themselves. During validation of objects, arrays or type choices, references to group specifications are replaced with their referenced content. For example, the the_bradys rule in Figure 70 is validated as if it were:

$the_bradys = [ "Mike", "Carol", "Greg", "Marsha", "Bobby", "Jan" ]

Figure 71

6.17.1. Groups in Arrays

Groups may be a subordinate component of array specifications:

[ ( ipv4 | ipv6 ), integer ]

Figure 72

Unlike primitive specifications, subordinate group specifications in array specifications may have sequence combinations and contain any type specification.

; a group in an array
[ ( $first_name, $middle_name ?, $last_name ), $age ]

; a group referenced from an array
[ $name, $age ]
$name = ( $first_name, $middle_name ?, $last_name )

$first_name = string
$middle_name = string
$last_name = string
$age = 0..

Figure 73

6.17.2. Groups in Objects

Groups may be a subordinate component of object specifications: Subordinate group specifications in object specifications may have sequence combinations but must only contain member specifications.

; a group in an object
{ ( $title, $date, $author ), $paragraph + }

; a group referenced from an object
{ $front_matter, $paragraph + }
$front_matter = ( $title, $date, $author )

$title = "title" : string
$date = "date" : date
$author = "author" : [ string * ]
$paragraph = /^p[0-9]*$/ : string

Figure 74

6.17.3. Group Rules as Macros

The syntax for group specifications accommodates one or more subordinate components and a repetition expression for each. Other than grouping multiple rules, a group specification can be used as a macro definition for a single rule.

$paragraphs = ( /^p[0-9]*$/ : string + )

Figure 75

This differs from a member specification because it includes a repetition specification.

6.18. Starting Points and Root Rules

Evaluation of a JSON instance or document against a ruleset begins with the evaluation of a root rule or set of root rules. If no root rule (or rules) is specified locally at runtime, the set of root rules specified in the ruleset are evaluated. The order of evaluation is undefined.

The set of root rules specified in a ruleset is composed of all rules without a rule name assignment and all rules annotated with the "@{root}" annotation.

The "@{root}" annotation may either appear before a rule name assignment or before a type definition. It is an error if present before referenced rule name inside of a type specification.

@{root} $request = { "cmd" : string }
$response = @{root} { "reply" : string }
@{root} { "status" : string }
{ "error" : string }   ; An implicit root

Figure 76

6.19. Reverse Linking via the @{augments} Annotation

IETF protocols often specify a core protocol and then additional functionality is added over time by defining additional specifications that update the original core protocol. The @{augments} annotation supports this way of working.

Typically objects, arrays and groups will include rule name references that reference child rules. The @{augments} annotation allows the direction of that referencing to be reversed. It effectively allows a child rule to specify that a parent rule should be augmented with a rule name reference to itself (the child).

The parameters of the @{augments} annotation consist of one or more, space-separated rule name references to parent rules that should be augmented with the name of the rule that has the @{augments} annotation.

For example, the following rules:

$main = { "first" : integer }

$extension = @{augments $main} ( "extra" : string ? )

Figure 77

is equivalent to the following:

$main = { "first" : integer, $extension }

$extension = ( "extra" : string ? )

Figure 78

Note that the reverse linking can occur across multiple rulesets with different ruleset-ids. For example, an extension might be defined as:

#ruleset-id org.example.extension
#import org.example.core as core

$extension = @{augments $core.main} ( "extra" : string ? )

Figure 79

7. Tips and Tricks

7.1. Any Member with Any Value

Because member names may be specified with regular expressions, it is possible to construct a member rule that matches any member name. As an example, the following defines an object with a member with any name that has a value that is a string:

{ // : string }

Figure 80

The JSON below matches the above rule.

{ "foo" : "bar" }

Figure 81

Likewise, the JSON below also matches the same rule.

{ "fuzz" : "bazz" }

Figure 82

Constructing an object with a member of any name with any type would therefore take the form:

{ // : any }

Figure 83

The above rule matches not only the two JSON objects above, but the JSON object below.

{ "fuzz" : 1234 }

Figure 84

7.2. Lists of Values

Group specifications may be used to create enumerated lists of primitive data types, because primitive specifications may contain a group specification, which may have multiple primitive specifications. Because a primitive specification must resolve to a single data type, the group specification must only contain choice combinations.

Consider the following examples:

; either an IPv4 or IPv6 address
$address = ( ipv4 | ipv6 )

; allowable fruits
$fruits = ( "apple" | "banana" | "pear" )

Figure 85

7.3. Subordinate Dependencies

In object and array specifications, there may be situations in which it is necessary to condition the existence of a subordinate component on the existence of a sibling subordinate component. In other words, example_two should only be evaluated if example_one evaluates positively. Or put another way, a member of an object or an item of an array may be present only on the condition that another member or item is present.

In the following example, the referrer_uri member can only be present if the location_uri member is present.

; $referrer_uri can only be present if
; $location_uri is present
{ ( $location_uri, $referrer_uri? )? }

$location_uri = "locationURI" : uri
$referrer_uri = "referrerURI" : uri

Figure 86

For validation, the above optional group is equivalent to:

{ ( $location_uri, $referrer_uri? ) | () }

Figure 87

8. Legacy Features

JCR has evolved since its initial conception. Often this has been as a result of 'in-the-field' experience. As JCR has evolved, certain features have been discarded from the main specification, but should still be supported by implementations in order not to break environments where JCR is already deployed. This section lists these deprecated features.

For legacy support, rule name assignments to primitive type specifications may optionally use the character sequence '=:', or the token sequence '= type', instead of a single '=' character.

;rule name assignments for primitive types
;using the legacy =: and = type syntax
$foo          =: "foo"
$other_string = type string

Figure 88

9. Implementation Status

This section records the status of known implementations of the protocol defined by this specification at the time of posting of this Internet-Draft, and is based on a proposal described in [RFC7942]. The description of implementations in this section is intended to assist the IETF in its decision processes in progressing drafts to RFCs. Please note that the listing of any individual implementation here does not imply endorsement by the IETF. Furthermore, no effort has been spent to verify the information presented here that was supplied by IETF contributors. This is not intended as, and must not be construed to be, a catalog of available implementations or their features. Readers are advised to note that other implementations may exist.

According to [RFC7942], "this will allow reviewers and working groups to assign due consideration to documents that have the benefit of running code, which may serve as evidence of valuable experimentation and feedback that have made the implemented protocols more mature. It is up to the individual working groups to use this information as they see fit".

9.1. JCR Validator

The JCR Validator, written in Ruby, currently implements all portions of this specification, and has been used extensively to prototype various aspects of JCR under consideration. Its development has gone hand-in-hand with this specification.

This software is primarily produced by the American Registry for Internet Numbers (ARIN) and freely distributable under the ISC license.

Source code for this software is available on GitHub at [JCRVALIDATOR]. This software is also easily obtained as a Ruby Gem through the Ruby Gem system.

9.2. Codalogic JCR Parser

The Codalogic JCR Parser is a C++ implementation of a JCR parsing engine, and is a work in progress. It is targeted for the Windows platform.

This software is produced by Codalogic Ltd and freely distributable under the Gnu LGPL v3 license.

Source code is available on GitHub at [CLJCR].

9.3. JCR Java

JCR Java is a work in progress and currently only implements the parsing of JCR rulesets according to the ABNF using a custom parsing framework.

This software is produced by the American Registry for Internet Numbers (ARIN) and freely distributable under the MIT license.

Source code is available on BitBucket at [JCRJAVA].

10. ABNF Syntax

The following ABNF describes the syntax for JSON Content Rules. A text file containing these ABNF rules can be downloaded from [JCR_ABNF].

jcr              = *( sp-cmt / directive / root-rule / rule )

sp-cmt           = spaces / comment
spaces           = 1*( WSP / CR / LF )
DSPs             = ; Directive spaces
                   1*WSP /     ; When in one-line directive
                   1*sp-cmt   ; When in muti-line directive
comment          = ";" *comment-char comment-end-char
comment-char     = HTAB / %x20-10FFFF
                   ; Any char other than CR / LF
comment-end-char = CR / LF

directive        = "#" (one-line-directive / multi-line-directive)
one-line-directive = [ DSPs ] 
                   (directive-def / one-line-tbd-directive-d)
                   *WSP eol
multi-line-directive = "{" *sp-cmt
                   ( directive-def /
                   multi-line-tbd-directive-d )
                   *sp-cmt "}"
directive-def    = jcr-version-d / ruleset-id-d / import-d
jcr-version-d    = jcr-version-kw DSPs major-version
                   "." minor-version
                   *( DSPs "+" [ DSPs ] extension-id )
major-version    = non-neg-integer
minor-version    = non-neg-integer
extension-id     = id
id               = ALPHA *id-tail
id-tail          = %x21-7C / %x7E-10FFFF ; not spaces, not }
ruleset-id-d     = ruleset-id-kw DSPs ruleset-id
import-d         = import-kw DSPs ruleset-id
                   [ DSPs as-kw DSPs ruleset-id-alias ]
ruleset-id       = id
ruleset-id-alias = name
one-line-tbd-directive-d = directive-name
                   [ WSP one-line-directive-parameters ]
directive-name   = name
one-line-directive-parameters = *not-eol
not-eol          = HTAB / %x20-10FFFF
eol              = CR / LF
multi-line-tbd-directive-d = directive-name
                   [ 1*sp-cmt multi-line-directive-parameters ]
multi-line-directive-parameters = multi-line-parameters
multi-line-parameters = *(comment / q-string / regex /
not-multi-line-special = spaces / %x21 / %x23-2E / %x30-3A /
                   %x3C-7C / %x7E-10FFFF ; not ", /, ; or }

root-rule        = value-rule / group-rule

rule             = annotations "$" rule-name *sp-cmt
                   "=" *sp-cmt rule-def

rule-name        = name
target-rule-name = annotations "$"
                   [ ruleset-id-alias "." ]
name             = ALPHA *( ALPHA / DIGIT / "-" / "_" )

rule-def         = member-rule / type-designator rule-def-type-rule /
                   value-rule / group-rule / target-rule-name
type-designator  = type-kw 1*sp-cmt / ":" *sp-cmt
rule-def-type-rule = value-rule / type-choice
value-rule       = primitive-rule / array-rule / object-rule
member-rule      = annotations
                   member-name-spec *sp-cmt ":" *sp-cmt type-rule
member-name-spec = regex / q-string
type-rule        = value-rule / type-choice / target-rule-name
type-choice      = annotations "(" type-choice-items
                   *( choice-combiner type-choice-items ) ")"
type-choice-items = *sp-cmt ( type-choice / type-rule ) *sp-cmt

annotations      = *( "@{" *sp-cmt annotation-set *sp-cmt "}"
                   *sp-cmt )
annotation-set   = not-annotation / unordered-annotation /
                   root-annotation /
                   exclude-min-annotation / exclude-max-annotation /
                   default-annotation / format-annotation /
                   choice-annotation / augments-annotation /
not-annotation   = not-kw
unordered-annotation = unordered-kw
root-annotation  = root-kw
exclude-min-annotation = exclude-min-kw
exclude-max-annotation = exclude-max-kw
default-annotation = default-kw spaces primitive-value
primitive-value  = false-value / null-type / true-value /
                   float-value / integer-value / string-value
format-annotation = format-kw spaces id
choice-annotation = choice-kw
augments-annotation = augments-kw *(spaces target-rule-name)
tbd-annotation   = annotation-name [ spaces annotation-parameters ]
annotation-name  = name
annotation-parameters = multi-line-parameters

primitive-rule   = annotations primitive-def
primitive-def    = string-type / string-range / string-value /
                   null-type / boolean-type / true-value /
                   false-value / double-type / float-type /
                   float-range / float-value /
                   integer-type / integer-range / integer-value /
                   sized-int-type / sized-uint-type / ipv4-type /
                   ipv6-type / ipaddr-type / fqdn-type / idn-type /
                   uri-type / phone-type / email-type /
                   datetime-type / date-type / time-type /
                   hex-type / base32hex-type / base32-type /
                   base64url-type / base64-type / any
null-type        = null-kw
boolean-type     = boolean-kw
true-value       = true-kw
false-value      = false-kw
string-type      = string-kw
string-value     = q-string
string-range     = regex
double-type      = double-kw
float-type       = float-kw
float-range      = float-min ".." [ float-max ] / ".." float-max
float-min        = float
float-max        = float
float-value      = float
integer-type     = integer-kw
integer-range    = integer-min ".." [ integer-max ] /
                   ".." integer-max
integer-min      = integer
integer-max      = integer
integer-value    = integer
sized-int-type   = int-kw pos-integer
sized-uint-type  = uint-kw pos-integer
ipv4-type        = ipv4-kw
ipv6-type        = ipv6-kw
ipaddr-type      = ipaddr-kw
fqdn-type        = fqdn-kw
idn-type         = idn-kw
uri-type         = uri-kw [ ".." uri-scheme ]
phone-type       = phone-kw
email-type       = email-kw
datetime-type    = datetime-kw
date-type        = date-kw
time-type        = time-kw
hex-type         = hex-kw
base32hex-type   = base32hex-kw
base32-type      = base32-kw
base64url-type   = base64url-kw
base64-type      = base64-kw
any              = any-kw

object-rule      = annotations "{" *sp-cmt
                   [ object-items *sp-cmt ] "}"
object-items     = object-item [ 1*( sequence-combiner object-item ) /
                   1*( choice-combiner object-item ) ]
object-item      = object-item-types *sp-cmt [ repetition *sp-cmt ]
object-item-types = object-group / member-rule / target-rule-name
object-group     = annotations "(" *sp-cmt [ object-items *sp-cmt ] ")"

array-rule       = annotations "[" *sp-cmt [ array-items *sp-cmt ] "]"
array-items      = array-item [ 1*( sequence-combiner array-item ) /
                   1*( choice-combiner array-item ) ]
array-item       = array-item-types *sp-cmt [ repetition *sp-cmt ]
array-item-types = array-group / type-rule
array-group      = annotations "(" *sp-cmt [ array-items *sp-cmt ] ")"

group-rule       = annotations "(" *sp-cmt [ group-items *sp-cmt ] ")"
group-items      = group-item [ 1*( sequence-combiner group-item ) /
                   1*( choice-combiner group-item ) ]
group-item       = group-item-types *sp-cmt [ repetition *sp-cmt ]
group-item-types = group-group / member-rule / type-rule
group-group      = group-rule

sequence-combiner = "," *sp-cmt
choice-combiner  = "|" *sp-cmt

repetition       = optional / one-or-more /
                   repetition-range / zero-or-more
optional         = "?"
one-or-more      = "+" [ repetition-step ]
zero-or-more     = "*" [ repetition-step ]
repetition-range = "*" *sp-cmt (
                   min-max-repetition / min-repetition /
                   max-repetition / specific-repetition )
min-max-repetition = min-repeat ".." max-repeat
                   [ repetition-step ]
min-repetition   = min-repeat ".." [ repetition-step ]
max-repetition   = ".."  max-repeat [ repetition-step ]
min-repeat       = non-neg-integer
max-repeat       = non-neg-integer
specific-repetition = non-neg-integer
repetition-step  = "%" step-size
step-size        = non-neg-integer

integer          = "0" / ["-"] pos-integer
non-neg-integer  = "0" / pos-integer
pos-integer      = digit1-9 *DIGIT

float            = [ minus ] int frac [ exp ]
                   ; From RFC 7159 except 'frac' required
minus            = %x2D                          ; -
plus             = %x2B                          ; +
int              = zero / ( digit1-9 *DIGIT )
digit1-9         = %x31-39                       ; 1-9
frac             = decimal-point 1*DIGIT
decimal-point    = %x2E                          ; .
exp              = e [ minus / plus ] 1*DIGIT
e                = %x65 / %x45                   ; e E
zero             = %x30                          ; 0

q-string         = quotation-mark *char quotation-mark 
                   ; From RFC 7159
char             = unescaped /
                   escape (
                   %x22 /          ; "    quotation mark  U+0022
                   %x5C /          ; \    reverse solidus U+005C
                   %x2F /          ; /    solidus         U+002F
                   %x62 /          ; b    backspace       U+0008
                   %x66 /          ; f    form feed       U+000C
                   %x6E /          ; n    line feed       U+000A
                   %x72 /          ; r    carriage return U+000D
                   %x74 /          ; t    tab             U+0009
                   %x75 4HEXDIG )  ; uXXXX                U+XXXX
escape           = %x5C              ; \
quotation-mark   = %x22      ; "
unescaped        = %x20-21 / %x23-5B / %x5D-10FFFF

regex            = "/" *( escape re-escape-code / not-slash ) "/"
                   [ regex-modifiers ]
re-escape-code   = %x20-7F ; Specific codes listed elsewhere
not-slash        = HTAB / CR / LF / %x20-2E / %x30-10FFFF
                   ; Any char except "/"
regex-modifiers  = *( "i" / "s" / "x" )

uri-scheme       = 1*ALPHA

;; Keywords
any-kw           = %x61.6E.79                      ; "any"
as-kw            = %x61.73                         ; "as"
augments-kw      = %x61.75.67.6D.65.6E.74.73       ; "augments"
base32-kw        = %x62.             ; "base32"
base32hex-kw     = %x62.    ; "base32hex"
base64-kw        = %x62.             ; "base64"
base64url-kw     = %x62.    ; "base64url"
boolean-kw       = %x62.6F.6F.6C.65.61.6E          ; "boolean"
choice-kw        = %x63.68.6F.69.63.65             ; "choice"
date-kw          = %x64.61.74.65                   ; "date"
datetime-kw      = %x64.       ; "datetime"
default-kw       = %x64.          ; "default"
double-kw        = %x64.6F.75.62.6C.65             ; "double"
email-kw         = %x65.6D.61.69.6C                ; "email"
exclude-max-kw   = %x65.78.63.6C. ; "exclude-max"
exclude-min-kw   = %x65.78.63.6C. ; "exclude-min"
false-kw         = %x66.61.6C.73.65                ; "false"
float-kw         = %x66.6C.6F.61.74                ; "float"
format-kw        = %x66.6F.72.6D.61.74             ; "format"
fqdn-kw          = %x66.71.64.6E                   ; "fqdn"
hex-kw           = %x68.65.78                      ; "hex"
idn-kw           = %x69.64.6E                      ; "idn"
import-kw        = %x69.6D.70.6F.72.74             ; "import"
int-kw           = %x69.6E.74                      ; "int"
integer-kw       = %x69.6E.          ; "integer"
ipaddr-kw        = %x69.             ; "ipaddr"
ipv4-kw          = %x69.70.76.34                   ; "ipv4"
ipv6-kw          = %x69.70.76.36                   ; "ipv6"
jcr-version-kw   = %x6A.63.72.2D. ; "jcr-version"
not-kw           = %x6E.6F.74                      ; "not"
null-kw          = %x6E.75.6C.6C                   ; "null"
phone-kw         = %x70.68.6F.6E.65                ; "phone"
root-kw          = %x72.6F.6F.74                   ; "root"
ruleset-id-kw    = %x72.75.6C. ; "ruleset-id"
string-kw        = %x73.             ; "string"
time-kw          = %x74.69.6D.65                   ; "time"
true-kw          = %x74.72.75.65                   ; "true"
type-kw          = %x74.79.70.65                   ; "type"
uint-kw          = %x75.69.6E.74                   ; "uint"
unordered-kw     = %x75.6E.6F.    ; "unordered"
uri-kw           = %x75.72.69                      ; "uri"

;; Referenced RFC 5234 Core Rules
ALPHA            = %x41-5A / %x61-7A   ; A-Z / a-z
CR               = %x0D         ; carriage return
DIGIT            = %x30-39      ; 0-9
HEXDIG           = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
HTAB             = %x09         ; horizontal tab
LF               = %x0A         ; linefeed
SP               = %x20         ; space
WSP              = SP / HTAB    ; white space

Figure 89: ABNF for JSON Content Rules

11. Security Considerations

The usage scenarios of JCR parallel that of ABNF. As such, when used in protocol specification, software development and test contexts, JCR should not present any security issues.

If JCR is used for validation in production environments, implementors are advised not to download rulesets on-the-fly, as this offers an additional attack vector for hackers, which could allow invalid JSON to be accepted as valid.

12. Acknowledgements

John Cowan, Andrew Biggs, Paul Kyzivat and Paul Jones provided feedback and suggestions which led to many changes in the syntax.

13. References

13.1. Normative References

[IEEE754] Institute of Electrical and Electronics Engineers, "Standard for Floating-Point Arithmetic", IEEE Standard 754, August 2008.
[JCR_ABNF] Newton, A. and P. Cordell, "ABNF for JSON Content Rules"
[RFC1166] Kirkpatrick, S., Stahl, M. and M. Recker, "Internet numbers", RFC 1166, DOI 10.17487/RFC1166, July 1990.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997.
[RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet: Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002.
[RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, DOI 10.17487/RFC3629, November 2003.
[RFC3986] Berners-Lee, T., Fielding, R. and L. Masinter, "Uniform Resource Identifier (URI): Generic Syntax", STD 66, RFC 3986, DOI 10.17487/RFC3986, January 2005.
[RFC4234] Crocker, D. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", RFC 4234, DOI 10.17487/RFC4234, October 2005.
[RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006.
[RFC5322] Resnick, P., "Internet Message Format", RFC 5322, DOI 10.17487/RFC5322, October 2008.
[RFC5952] Kawamura, S. and M. Kawashima, "A Recommendation for IPv6 Address Text Representation", RFC 5952, DOI 10.17487/RFC5952, August 2010.
[RFC8259] Bray, T., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, December 2017.

13.2. Infomative References

[I-D.cordell-jcr-co-constraints] Cordell, P. and A. Newton, "Co-Constraints for JSON Content Rules", Internet-Draft draft-cordell-jcr-co-constraints-00, March 2016.
[RFC7493] Bray, T., "The I-JSON Message Format", RFC 7493, DOI 10.17487/RFC7493, March 2015.
[RFC7942] Sheffer, Y. and A. Farrel, "Improving Awareness of Running Code: The Implementation Status Section", BCP 205, RFC 7942, DOI 10.17487/RFC7942, July 2016.

13.3. Resource References

[CLJCR] "Codalogic JCR Parser"
[FIGS] "JCR Document Figures"

Appendix A. Experimental Features

The following features are in development and may appear in a future version of this specification.

A.1. Augmented OR of Objects

Augmented OR of objects is an algorithm for better writing of OR, where the current approach uses simple inclusive OR. The design goal behind augmented OR is to give the writer and the reader a "that's what I meant" solution.

For example, take the following rule for an object where two members are given as a choice.

{ "foo":string | "bar":integer }

Figure 90

The interpretation of Figure 90 is that the object may either contain a member named "foo" that is a string or a member named "bar" that is an integer. To some, that raises a number of questions:

With normal inclusive OR, it might be necessary to write more complicated rules to disambiguate the desired contents of the object, as this rule demonstrates.

  ( "foo":string , @{not} "bar":any ) |
  ( "bar":integer, @{not} "foo":any )

Figure 91

The augmented OR algorithm for objects would allow Figure 90 to mean the equivalent as the Figure 91 using inclusive OR.

A.2. New Data Types

JCR is intentionally designed with a rich set of data types to eas the burden of reading and writing rules.

The following are a list of new data types under consideration:

A.3. New Annotations

JCR can be extended through the use of annotation but also has a set of standard annotations. The design philosophy for annotations are to provide features to the language that are not as common or do not have a clear, less-wordy syntax.

The following annotations are under consideration:

Appendix B. Co-Constraints

This specification defines a small set of annotations and directives for JCR, yet the syntax is extensible allowing for other annotations and directives. [I-D.cordell-jcr-co-constraints] ("Co-Constraints for JCR") defines further annotations and directives which define more detailed constraints on JSON messages, including co-constraints (constraining parts of JSON message based on another part of a JSON message).

Appendix C. Testing Against JSON Content Rules

One aspect of JCR that differentiates it from other format schema languages are the mechanisms helpful to developers for taking a formal specification, such as that found in an RFC, and evolving it into unit tests, which are essential to producing quality protocol implementations.

C.1. Locally Overriding Rules

As mentioned in the introduction, one tool for testing would be the ability to locally override named rules. As an example, consider the following rule which defines an array of strings.

$statuses = [ string * ]

Figure 92

Consider the specification where this rule is found does not define the values but references an extensible list of possible values updated independently of the specification, such as in an IANA registry.

If a software developer desired to test a specific situation in which the array must at least contain the status "accepted", the rules from the specification could be used and the statuses rule could be explicitly overridden locally as:

This rule will evaluate positively with the JSON in Figure 94

$statuses = @{unordered} [ "accepted", string * ]

Figure 93

[ "submitted", "validated", "accepted" ]

Figure 94

Alternatively, the developer may need to ensure that the status "denied" should not be present in the array:

This rule will fail to evaluate the JSON in Figure 96 thus signaling a problem.

$statuses = @{unordered} @{not} [ "denied" + , string * ]

Figure 95

[ "submitted", "validated", "denied" ]

Figure 96

C.2. Rule Callbacks

In many testing scenarios, the evaluation of rules may become more complex than that which can be expressed in JCR, sometimes involving variables and interdependencies which can only be expressed in a programming language.

A JCR processor may provide a mechanism for the execution of local functions or methods based on the name of a rule being evaluated. Such a mechanism could pass to the function the data to be evaluated, and that function could return to the processor the result of evaluating the data in the function.

Appendix D. Changes from -09 and -10

The syntax of JCR was changed so that rule name assignments to primitive types no longer requires the '=:' syntax. The '=:' is still provided for backwards compatibility. Other syntax changes have been made to accommodate the syntax of future annotations.

Authors' Addresses

Andrew Lee Newton American Registry for Internet Numbers PO Box 232290 Centreville, VA 20120 US EMail: URI:
Pete Cordell Codalogic PO Box 30 Ipswich, IP5 2WY UK EMail: URI: