Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions draft-vasters-json-structure-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,47 @@ schema definition, including in type unions.
`$ref` is NOT permitted in other attributes and MUST NOT be used inside the
`type` of the root object.

### `targettype` Keyword {#targettype-keyword}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other possible keyword names:

Suggested change
### `targettype` Keyword {#targettype-keyword}
### `target` Keyword {#targettype-keyword}
Suggested change
### `targettype` Keyword {#targettype-keyword}
### `targetobject` Keyword {#targettype-keyword}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the direction of this, but I would make this broader:

For objects and tuples, I would add a new keyword "relations" that is similar to "properties" but specifically defines relationship properties. "relations" and "properties" share a namespace, so they can't define conflicting names.

The "relations" declarations are not modeled as types, because they must be able to cross-reference properties within the same tuple/object. They are similar to properties in that they are represented just like properties in the instances.

A relation is a named object (via the relations map) that has two properties:

  • the targettype declares the target type that the relation refers to
  • the cardinality declares whether the relationship point to one or more targets

the targettype's identity declaration functions like the tuple keyword for establishing the type of references, meaning that if the target's identity clause references two properties, the reference value in the relation source is a tuple-encoded list of values matching that identity.

An instance of a single relation is an object with the following properties:

"ref" : a JSON pointer to the target object
"identity": a tuple that reflects the target object identity values

A relationship MAY be established either through a direct link (ref) or through an identity match against all known instances of the target type. If both properties are defined, the identity match is performed against all instances of the target type that exists within the scope of the ref json pointer, i.e. are children of the identified node.

the value of relations with multiple cardinality is an array of such objects. the value of relations with single cardinality is a single such object.

{
  "$schema": "https://json-structure.org/meta/core/v0/#",
  "$id":     "https://example.com/library.schema",
  "$root":   "#/definitions/Library/Document",

  "definitions": {
    "Library": {

      /* 1 ─────────  AUTHOR  ───────── */
      "Author": {
        "type": "object",
        "name": "Author",
        "properties": {
          "id":   { "type": "uuid" },
          "name": { "type": "string" }
        },
        "required": ["id", "name"],
        "identity": ["id"]
      },

      /* 2 ────────  PUBLISHER  ─────── */
      "Publisher": {
        "type": "object",
        "name": "Publisher",
        "properties": {
          "id":   { "type": "uuid" },
          "name": { "type": "string" },
          "city": { "type": "string" }
        },
        "required": ["id", "name"],
        "identity": ["id"]
      },

      /* 3 ──────────  BOOK  ─────────── */
      "Book": {
        "type": "object",
        "name": "Book",

        /* intrinsic fields only — reference
           properties are implicit via `relations` */
        "properties": {
          "isbn":  { "type": "string" },
          "title": { "type": "string" }
        },
        "required": ["isbn", "title"],
        "identity": ["isbn"],

        "relations": {
          /* many Authors per Book  */
          "authors": {
            "cardinality": "multiple",
            "targettype":  { "$ref": "#/definitions/Library/Author" }
          },
          /* exactly one Publisher per Book */
          "publisher": {
            "cardinality": "single",
            "targettype":  { "$ref": "#/definitions/Library/Publisher" }
          }
        }
      },

      /* 4 ────────  DOCUMENT ROOT  ─────── */
      "Document": {
        "type": "object",
        "name": "LibraryDocument",
        "properties": {
          /* list of all authors present in this file */
          "authors": {
            "type": "array",
            "items": { "$ref": "#/definitions/Library/Author" }
          },

          /* list of all publishers */
          "publishers": {
            "type": "array",
            "items": { "$ref": "#/definitions/Library/Publisher" }
          },

          /* list of all books */
          "books": {
            "type": "array",
            "items": { "$ref": "#/definitions/Library/Book" }
          }
        }
      }
    }
  }
}

Instance:

{
  "$schema": "https://example.com/library.schema",
  "authors": [
    {
      "id":   "3aad369c-1bfb-11e5-9a21-1697f925ec7b",
      "name": "Brian W. Kernighan"
    },
    {
      "id":   "b3e0fac1-603f-4960-8646-90b053b6af19",
      "name": "Dennis M. Ritchie"
    }
  ],
  "publishers": [
    {
      "id":   "9609d302-62fb-4d0a-9e71-86f744e3022c",
      "name": "Prentice Hall"
    }
  ],
  "books": [
    {
      "isbn":  "978-0131103627",
      "title": "The C Programming Language",
      "authors": [
        {
          "ref":      "#/authors/0",
          "identity": ["3aad369c-1bfb-11e5-9a21-1697f925ec7b"]
        },
        {
          "ref":      "#/authors/1",
          "identity": ["b3e0fac1-603f-4960-8646-90b053b6af19"]
        }
      ],
      "publisher": {
        "ref":      "#/publishers/0",
        "identity": ["9609d302-62fb-4d0a-9e71-86f744e3022c"]
      }
    }
  ]
}

I explicitly made the relation instance an object, because that gives us the opportunity to declare a qualifier type in the relation which defines/references a type to qualify the relationship further, equivalent to link properties in a graph.

Example:

qualifier type:

"AuthorRole": {
  "type": "string",
  "enum": ["Author", "Editor", "Illustrator", "Translator"]
}

extend the relation:

"authors": {
  "targettype":  { "$ref": "#/definitions/Library/Author" },
  "cardinality": "multiple",

  /* NEW – the schema that qualifies the link                *
   * Every relation-instance object MUST carry a `qualifier`  *
   * that validates against this type.                       */
  "qualifiertype": { "$ref": "#/definitions/Library/AuthorRole" }
}

Instance with qualifiers:

{
  "$schema": "https://example.com/library.schema",

  "authors": [
    { "id": "3aad369c-1bfb-11e5-9a21-1697f925ec7b", "name": "Brian W. Kernighan" },
    { "id": "b3e0fac1-603f-4960-8646-90b053b6af19", "name": "Dennis M. Ritchie" }
  ],

  "publishers": [
    { "id": "9609d302-62fb-4d0a-9e71-86f744e3022c", "name": "Prentice Hall" }
  ],

  "books": [
    {
      "isbn":  "978-0131103627",
      "title": "The C Programming Language",

      "authors": [
        {
          "ref":       "#/authors/0",
          "identity":  ["3aad369c-1bfb-11e5-9a21-1697f925ec7b"],
          "qualifier": "Author"          // ← validated by AuthorRole enum
        },
        {
          "ref":       "#/authors/1",
          "identity":  ["b3e0fac1-603f-4960-8646-90b053b6af19"],
          "qualifier": "Author"
        }
      ],

      "publisher": {
        "ref":       "#/publishers/0",
        "identity":  ["9609d302-62fb-4d0a-9e71-86f744e3022c"]
        /* no qualifier here because the relation         *
         * lacks a `qualifiertype` in the declaration.    */
      }
    }
  ]
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like where this is going, thanks for creating this example!
Moving it to its own relation property as an array of object seems like a really good idea.

Where I'm unsure is if we can assume that the instances are structured like you propose. I see how it's nice to make each reference an object and for convenience also adding a "ref", but in many real data cases I've seen the references are just a string property or an array of strings. The concept also needs to work with this. So the "reference" in the schema has to point to the property that carries the ID of the reference, too?

Let me get back on this to you when I have more time to think it through.


The `targettype` keyword can be used to indicate an association / reference /
pointer to an object, using its primary IDs. With this information, consumers
know how data can be joined together and how to construct references correctly.

Note: Only in the case of a single primary ID it's clear how the reference
property maps to the target object ID. In case of composite IDs, the
references become composite references, where the mapping from multiple
reference properties to multiple target IDs need to be explicitly maintained.
This goes beyond the scope of the core spec.

Example:

~~~ json
{
"$schema": "https://json-structure.org/meta/core/v0/#",
"$id": "https://schemas.vasters.com/TypeName",
"definitions": {
"Namespace": {
"Person": {
"name": "Person",
"type": "object",
"id": ["ID"],
"properties": {
"ID": { "type": "string" },
"archenemy": {
"type": "string"
"targettype": ""$ref": "#/Namespace/Person""
},
}
},
}
}
}
~~~

The `targettype` MUST only be used on object properties.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@clemensv : Could it also appear in set and tuple?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You clarified it in another comment.

Suggested change
The `targettype` MUST only be used on object properties.
The `targettype` is optional, but only applicable for object or tuple properties.

The value MUST be a valid JSON Pointer to the association target object,
which MUST provide the `id` keyword to indicate its primary IDs.

### Cross-references {#cross-references}

In JSON Structure documents, the `$schema` keyword references the meta-schema of
Expand Down