Skip to content

Conversation

Fannon
Copy link

@Fannon Fannon commented Apr 29, 2025

Relates #4

Builds upon #7

Adds the targettype keyword to indicate associations / references / pointers within the model.

Signed-off-by: Simon Heimler <simon.heimler@sap.com>
@@ -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` 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.

@clemensv
Copy link
Contributor

clemensv commented Jul 2, 2025

I think identities and relations would be a good companion spec.

@clemensv clemensv added the companion spec candidate topic for a companion spec label Jul 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
companion spec candidate topic for a companion spec
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants