ghislainfourny / jsound-for-yaml

Compact JSound syntax for YAML

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Getting started with JSound for YAML 2.0

This tutorial gives an introduction to JSound 2.0, starting with its compact syntax for YAML (credits for the idea: Dana Florescu).

Note: not all the content below has be retranscripted into YAML, but this will come.

JSound's type system

JSound for YAML 2.0 is a schema language that validates and annotates YAML documents.

Whereas some other JSON Schema languages are based on filtering JSON documents, a JSound schema is best seen as a collection of type declarations.

JSound for YAML supports four kinds of types corresponding to YAML's spirit:

  • atomic types, with a selection of builtin, ready-to-use types: string, integer, date, hexBinary to name a few
  • object types, to validate and annotate JSON objects
  • array types, to validate and annotate JSON arrays
  • union types, to combine several types and union their value spaces

JSON and YAML

Note that a JSound for YAML schema can be used to validate both JSON and YAML syntax. Likewise, a "classical" JSound schema can be used to validate both JSON and YAML syntax.

A hello world

Let us start with a simple example.

This is a YAML instance:

name: JSound

Imagine we would like to validate such instances against a type, let us call it my-type, and say that for objects in that type's value space, the name field must be a string.

The type declaration itself, in JSound's comptact syntax, mimics the instance, like so:

name: string

Now, if we want to put it inside a JSound schema and give the type a name, we associate this type declaration with the type name like so:

my-type:
  name: string

Note that this type declaration does not prevent further fields from being present, which is the default behavior. Also, it does not require the field name to be present -- but if it is, it must be a string.

These are a few instances valid against my-type:

name: James Kirk
name: Beverly Crusher
name: Spock
origin: Vulcan
age: 234
spaceship: USS Discovery
name: Michael Burnham
century: 23

And a few that are not:

name: 1
name: null
name:
  - Spock
age: 123
bar: 1
name:
  first: James
  last: Kirk

More on object type declarations

Requiring a field

In order to make the name field required, we can add an exclamation mark, like so:

my-type
  "!name": string

Then, these documents become invalid:

century: 23

Type names containing ! are possible, but this requires the advanced syntax, covered later.

Default value

We can also supply a default value, like so:

my-type:
  name: string=N/A

Then the following instances would be valid:

century: 23

But an annotation step would actual produce a different output, populating default values:

name: N/A
century: 23
name: N/A

Default values containing = are possible, but this requires the advanced syntax, covered later.

More advanced features are available, such as not allowing extra fields, but we will cover this later with the advanced syntax.

nullability

A question mark indicates that null is allowed:

my-type:
  name: "string?"

This instance is then valid:

name: null

Technically, what is defined here is a union type with the member types string and null.

More builtin atomic types

We used so far the string builtin type. There are a few others builtin atomic types.

The validation of an atomic type is done based on its lexical value. Whether it is quoted or not is totally irrelevant, as long as the input is well-formed YAML.

We provide below the list of builtin types, and for simplicity simple give examples of valid instances.

For all examples, we give a very simple schema with just one field that has the builtin type being explained.

For further reference, these types (except null) are documented in the XML Schema specification.

integer

my-type:
  "!field": integer

The following instances are valid:

field: 1
field: 3
field: 12
field: 0
field: -3
field: 123450987234502983452345

decimal

my-type:
  "!field": decimal

The following instances are valid:

field: 1
field: 3
field: 12.3
field: 0
field: -334.23
field: 123450987234502983452345.23405978234059872345023945809823745

double

my-type:
  "!field": double

The following instances are valid:

field" : 1
field:  1
field: 12.3
field: 2345e78
field: -1234.2345e-345

boolean

my-type:
  "!field": boolean

The following instances are valid:

field: true
field: false

anyURI

my-type:
  "!field": anyURI

The following instances are valid:

field: http://www.example.com

(Actually, any string is accepted, but it signals an intent to include URIs).

base64Binary

my-type:
  "!field": base64Binary

The following instances are valid:

field: SGVsbG8sIHdvcmxk

hexBinary

my-type:
  "!field": hexBinary

The following instances are valid:

field: 8a08b0c0908f

date

my-type:
  "!field": date

The following instances are valid:

field: 2019-01-19

dateTime

my-type:
  "!field": dateTime

The following instances are valid:

field: 2019-01-19T12:00:00
field: 2019-01-19T12:34:56.789
field: 2019-01-19T12:00:00.000Z
field: 2019-01-19T12:00:00.000+02:00

time

my-type:
  "!field": time

The following instances are valid:

field: 12:00:00
field: "2:34:56.789
field: 12:00:00.000Z
field: 12:00:00.000+02:00

dateTimeStamp

my-type:
  "!field": dateTimeStamp

The following instances are valid:

{ "field" : "2019-01-19T12:34:56.789-08:00" }
{ "field" : "2019-01-19T12:00:00.000Z" }
{ "field" : "2019-01-19T12:00:00.000+02:00" }

duration

my-type:
  "!field": duration

The following instances are valid:

{ "field" : "P1Y2M3DT4H5M6S" }
{ "field" : "P1Y2M3D" }
{ "field" : "P3D" }
{ "field" : "P3DT4H5M6S" }
{ "field" : "PT4H5M6S" }
{ "field" : "PT6S" }

null

my-type:
  "!field": null

The following instances are valid:

{ "field" : null }
{ "field" : "null" }

Arrays

Array types can be defined in the simplified syntax as well by specifying the type of its members.

For example, we can define an array of integers like so:

my-type:
  "!field":
    - integer

The following instances are valid:

{ "field" : [ 1, 2, 3, 4 ] }
{ "field" : [ 1 ] }
{ "field" : [ ] }

Arrays of objects

We can also nest type declarations and, for example, define an array of objects matching a certain type:

my-type:
  "!field":
    "!first": string
    "!last": string=N/A
    "!age": integer

The following instances are valid -- as you can see, the schema looks very similar to them, which makes it easy to read:

{ "field" : [ { "first" : "James", "last" : "Kirk", "age" : 30 } ]
{
  "field" : [
    { "first" : "James", "last" : "Kirk", "age" : 30 },
    { "first" : "Kathryn", "last" : "Janeway", "age" : 50 },
    { "first" : "Spock", "age" : 234 }
  ]
}
{ "field" : [ ] }

It is also possible to name the object type and refer to it. The following schema is equivalent:

person:
  "!first": string
  "!last": string=N/A
  "!age": integer
list:
  field:
    - person

It is also possible to require uniqueness within an array of objects. For example, we can require that persons have different IDs with the @ symbol:

my-type:
  "!field":
    -
      "@id": integer
      "!first": string
      "!last": string=N/A
      "!age": integer

The following instance is then valid:

field:
  -
    id: 1
    first: James
    last: Kirk
    age: 30
  -
    id: 2
    first: Kathryn
    last: Janeway
    age: 50
  -
    id: 3
    first: Spock
    age: 234

But not this one:

field:
  -
    id: 1
    first: James
    last: Kirk
    age: 30
  -
    id: 2
    first: Kathryn
    last: Janeway
    age: 50
  -
    id: 2
    first: Spock
    age: 234

Union types

Unions can be defined in field types with the | symbol.

my-type:
  integers-or-booleans:
    - integer|boolean

Valid instance:

integers-or-booleans:
  - 1
  - 3
  - true
  - 4
  - false

As a reminder, "?" is a shortcut for "|null".

Predefined types

JSound for YAML also has predefined types: "atomic" contains all atomic values (it can be seen as the union of all of atomic types). "object" contains all objects, "array" contains all arrays and "value" contains any YAML values: atomic, object or array.

Annotation

JSound for YAML 2.0 also supports annotation. Annotation means that an input instance is taken, and a TYSON instance is output. TYSON is simply JSON with type annotations.

For example, let us consider the following schema:

person:
  first: string
  middle: "string?"
  last: string=N/A
  age: integer
  picture: hexBinary
persons-array:
  - person
persons:
  list: persons-array

And the following instance, valid against the type persons:

list:
  -
    first: James
    middle: null
    last: Kirk
    picture" : "0123456789abcdef
  -
    first: Spock
    middle: S
    picture: aaaaaaaaaaaaaaaaaaa

Annotation will output the following TYSON instance, where all values are associated with a type, and default values are populated:

("persons") {
  "list" : ("persons-array") [
    ("person") {
      "first" : ("string") "James",
      "middle" : ("null") null,
      "last" : ("string") "Kirk",
      "picture" : ("hexBinary") "0123456789abcdef"
    },
    ("person") {
      "first" : ("string") "Spock",
      "middle" : ("string") "S",
      "last" : ("string") "N/A",
      "picture" : ("hexBinary") "aaaaaaaaaaaaaaaaaaa"
    }
  ]
}

This TYSON instance can be sent over the network to somebody else (together with the corresponding schema containing the type definitions), stored somewhere for later processing, converted to a language's data model, for example a Java object, and so on.

Advanced syntax

Whenever advanced functionality is needed, such as names with special characters, disallowing extra fields, ..., the schema designer can switch to the advanced, more verbose JSound syntax, which still builds on the same type system and data model. Another tutorial will cover it.

About

Compact JSound syntax for YAML

License:Apache License 2.0