Connected JSON Specification

1. Introduction

We want a JSON-based document for exchanging graphs. Graphs contain nodes and edges. Undirected edges, directed edges (DAG), typed edges (Hello RDF), weighted edges (Hello flow algorithms) and even hyper-edges (Hello biologists). We want sub-graphs (Hello diagrams). We want data attached to nodes and edges (Hello knowledge graphs).

1.1. Goals and Motivation

Yes, we know, but the last effort (JSON Graph Format) is over 10 years old and GraphML over 20 years by now. And some GraphML features (mixed hyper-edges, nested graphs) are not supported in JGF. In fact, none of the existing JSON graph interchange formats has the same depth of features as the over 20-year-old XML-based GraphML.

Connected JSON aims to be a full GraphML replacement. It supports the semantic capabilities and data representation found in GraphML, while adopting a more flexible, schema-less JSON approach. Different from GraphML, this format prioritizes flexible data attachment directly onto elements, alongside the core graph structure: Any element can carry arbitrary additional properties.

This format is intended as the universal interchange format for all kinds of graphs, which can be as complex, as what GraphML allows — and that is a lot. Together with the Vocabulary and Aliases extension, it is even a super-set of JSON Graph Format.

Non-goals
  • Massive-scale (> 1 GB) graphs requiring streaming (its a JSON format after all)

  • Highly dynamic graphs, which change more often than every minute. Those require a delta protocol, not a file format.

1.2. Example

Connected JSON Example File
{
  "nodes": [
    { "id":  12 },
    { "id":  "a",
      "ports": [ "a1",
                 { "id": "a2",
                   "ports": [ "a2-1", "a2-2" ]
    }]},
    { "id":  "b", "foo": "bar" },
    { "id":  "c" },
    { "id":  "d" },
    { "id":  "e" },
    { "id":  "f" }
  ],
  // Extensible. Only some keys reserved for CJ.
  "hello": ["My data","can be","everywhere"],
  "edges": [
    { "source": 12, "target": "a"},
    // in canonical form, can have ports:
    { "endpoints":  [
      { "direction": "in", "node": 12, "port":  "a2-1"},
      { "direction": "out", "node": "a"}
    ]},

    // ports only available in endpoints syntax
    { "endpoints":  [
      { "direction": "in", "node": 12},
      { "direction": "in", "node": "a", "port": "a2-1" },
      { "direction": "out", "node": "d"},
      { "direction": "out", "node": "e"}
    ]},

    // mixed edges only available in endpoints syntax
    { "endpoints":  [
      { "direction": "in", "node": 12},
      { "direction": "in", "node": "a"},
      { "direction": "out", "node": "d"},
      { "direction": "out", "node": "e"},
      { "direction": "undir", "node": "f"}
    ]}
  ]
}

1.3. About

  • File extension: .con.json or .con.json5 (JSON5 allows comments).

  • MIME type: application/connected+json

  • MIME type: application/connected+json5 (allowing comments)

See RFC 6839 for +json.

Feedback/Contribute

GitHub: https://github.com/Calpano/connected-json

License

MIT License
Copyright 2025 Max Völkel

1.4. Version History and Change Log

The versions use semantic versioning.

2025-07-03: Version 3.0.0
  • Renamed all properties with a dash to camelCase form. This makes is pragmatically easy to represent properties in programming languages as variable names or enum values.

    • type-nodetypeNode

    • type-uritypeUri

  • Renamed some lowercase properties to camelCase form. This avoids IDEs and editors complaining about spelling.

    • baseuribaseUri

    • edgedefaultedgeDefault

2025-06-26: Version 2.0.0
  • Multi-Lingual Labels switched from a JSON object with language tags as property keys to a more canonical array-form.

2025-04-30: Version 1.1.0
  • Clearer Identifiers section

  • Allow graph inside edge (consistent with diagram an GraphML)

2025-04-08: Version 1.0.0

Initial public release

2. Structure Overview

Every element can carry arbitrary JSON properties besides the ones interpreted by this spec.
Structure for Bi-Edges, without Ports, without Nested Graphs
Figure 1. Structure for Bi-Edges, without Ports, without Nested Graphs

A bi-edge has always exactly 2 endpoints.

Full Structure
Figure 2. Full Structure

3. Elements

3.1. Document

Every file is a document. Technically, a JSON object. It contains one or more graphs.

Property Type Description

graph

object(Graph) or array(Graph [])

Object for 1 graph, array for 1 to n graphs. Default: Empty.

The pattern of allowing both (a) a single object or (b) an array of a-objects, is used throughout the spec.
As an alternative (alias) to graph also the plural graphs is allowed. A list of allowed alias names is given in the Vocabulary and Aliases extension.

3.2. Graph

Contains one or more nodes and/or one or more edges.

Property Type Description

baseUri

string(URI)

Optional. Is used to fine-tune the Interpretation as RDF.

edgeDefault

string

Optional. Default is directed. Defines the default directedness for edges. Iff set to undirected, every Edge is implied to have set "directed": false.

edges

array(Edge [])

0 to n edges (which may be bi- oder hyperedges). Default: Empty.

id

string or number (integer)

Optional. Unique identifier for the graph within a Document. See Identifiers.

label

string

Label (name) of the node. Optional.

nodes

array(Node [])

0 to n nodes. Default: Empty.

Streaming

For streaming processing, the property order MUST be: (baseUri, id, label), (nodes, edges). The order within each brace group is not relevant.

3.3. Node

A node is an atom in the graph.

Property Type Description

graph

object (Graph) or array (Graph [])

Optional. Graph(s) nested within the node. This turns the node into a compound node. The edges in a sub-graph can refer to nodes higher up in the tree of graphs.

id

string or number (integer)

Required. Unique identifier for the node. See Identifiers.

label

string or object

Label (name) of the node. Optional. The object is used for Multi-Lingual Labels.

ports

array(Port [])

Optional array of ports.

Streaming

For streaming processing, the property order MUST be: (id, label), ports, graph. The order within the brace group is not relevant.

3.4. Port

A port is always a part of a Node. A layout should place a port on the border of the node widget. Ports may be hierarchically nested. This is used in practice graphical editors, where a port is a connection point on a node.

Property Type Description

id

string

ID unique within the Node. All ports, even nested one, share the same ID space per node. See also Identifiers.

label

string

Optional

ports

array(Port [])

Optional array of sub-ports. Recursively.

Streaming

For streaming processing, the property order MUST be: (id, label), ports. The order within the brace group is not relevant.

3.5. Edge

Uses endpoints to link to nodes. However, simple bi-edges with only two ends have a shortcut syntax.

The structural model for any edge is this:

Edge Model
Figure 3. Edge Model
  • An edge has n endpoints.

  • An endpoint defines the direction of the attached node, relative to the edge. Is the node incoming, outgoing or undirected (from the perspective of the edge).

  • A target can be a node or a port attached to a port. Yes, a port can also be nested within other ports, forming a kind of recursive port-tree. GraphML has this.

Edges have been modelled like GraphML. They have been extended with a type-property, to make it easier to express RDF.
Property Type Description

graph

object (Graph) or array (Graph [])

Optional. Graph(s) nested within the edge. This turns the edge into a compound edge. The edges in a sub-graph can refer to nodes higher up in the tree of graphs.

id

string or number (integer)

Optional id. Unique per graph. See Identifiers.

label

string or object

Label for the edge. Optional. The object is used for Multi-Lingual Labels.

source

string or number (integer) or array(node id []).

Either a single node id
or an array of node ids.

Shortcut syntax: All created endpoints are interpreted as incoming. I.e. "source": "n17" has the same effect as
"endpoint": { "node":"n17", "direction": "in" }. Ports are only available in endpoints syntax.

target

Shortcut syntax: All created endpoints are interpreted as outgoing. I.e.
"target": "n12" has the same effect as
"endpoint": { "node":"n12", "direction": "out" }. Ports are only available in endpoints syntax.

type

string

Optional. The kind of edge. See Edge Endpoint and Interpretation as RDF.

typeUri

string

typeNode

string or number (integer)

directed

boolean

Default: true (keeps stated endpoint directions, including undir). Can be set to false to define all endpoint directions as undir. See also edgeDefault on Graph.

endpoints

array (Edge Endpoint [])

This is the canonical way to express edges.

Streaming

For streaming processing, the property order MUST be: (id, label, type, typeUri, typeNode, directed), (source, target, endpoints), graph. The order within each brace group is not relevant.

Example: Directed Bi-Edge
{
  "source": "n12",
  "target": "n17"
}

is the same as

{
  "endpoints": [
    { "node": "n12",  "direction": "in"  },
    { "node": "n17",  "direction": "out" }
  ]
}

or

{
  "source": "n12",
  "target": "n17",
  "directed": true
}
Example: Undirected Bi-Edge
{
  "source": "n12",
  "target": "n17",
  "directed": false
}

is the same as

{
  "endpoints": [
    { "node": "n12",  "direction": "undir" },
    { "node": "n17",  "direction": "undir" }
  ]
}
Example: Directed Hyper-Edge
{
  "source": ["n12","n3","n123"],
  "target": ["n17","n100"]
}

is the same as

{
  "endpoints": [
    { "node": "n12",  "direction": "in"  },
    { "node": "n3",   "direction": "in"  },
    { "node": "n123", "direction": "in"  },
    { "node": "n17",  "direction": "out" },
    { "node": "n100", "direction": "out" }
  ]
}
Example: Undirected Hyper-Edge
{
  "source": ["n12","n3","n123"],
  "target": ["n17","n100"],
  "directed": false
}

is the same as

{
  "endpoints": [
    { "node": "n12",  "direction": "undir" },
    { "node": "n3",   "direction": "undir" },
    { "node": "n123", "direction": "undir" },
    { "node": "n17",  "direction": "undir" },
    { "node": "n100", "direction": "undir" }
  ]
}

3.6. Edge Endpoint

Property Type Description

direction

One of: in, out or undir

Optional. Maps to incoming (in), outgoing (out), or undirected (undir). Default is undir. See also: Graph edgeDefault.

node

string or number (integer)

Required. Node id. Either a string containing a single nodeId or a number (auto-converted to string) containing a single node id.

port

string or number (integer)

Optional. Port id. Port ids are only unique per node/port. See Identifiers.

type

string

Optional. The type of relation from the edge entity to the endpoint node. If a URI is given, us typeUri instead. This property states the relation as a string, e.g. works at or knows. Default is related.

typeUri

string(URI)

Optional. The type of relation from the edge entity to the endpoint node.

typeNode

string or number (integer)

Uses a node in the graph (referenced by node id, see Identifiers) to define the kind of relation. This is the same strategy that RDF uses: property URIs are RDF resources themselves, which can have a label and other edges attached to them.

Type
  • Either type, typeUri, or typeNode MAY be used. If several are given, typeUri has precedence, then typeNode, then type. See also Interpretation as RDF.

4. Features

4.1. Data

The structural elements are:

  • Document

  • Graph

  • Node

  • Port

  • Edge

  • Endpoint

Each of these can have arbitrary additional JSON properties, besides the ones defined in this spec. All additional properties are interpreted as data attached to the structural element. Nested JSON is allowed.

This can be used for style data (e.g. line-color) or domain data (e.g. population, sales volume).

4.2. Multi-Lingual Labels

A standard label "label": "Hello, World" has no language information. Multilingual labels in Connected JSON are stated like JSON-LD 1.1 (expanded)[1] does it:

{
  "label": [
    {"language":"de", "value": "Hallo, Welt"},
    {"language":"en", "value": "Hello, World"}
  ]
}

4.3. Nested Graphs

In Connected JSON, like in GraphML, nodes and edges can contain sub-graphs. Recursively. A sub-graph is similar to a top-level graph. However, (1) all nodes in a top-level graph, including all nodes nested within sub-graphs, recursively, share the same ID space. The same for edges. (2) Any edges, including those nested in nested graphs, may link to any node within the top-level graph, including those within nested graphs.

4.4. Edge Direction

The edge direction can be stated in three locations:

  1. Using the edgeDefault property on the whole Graph.

    • If set to directed, the edge and endpoint level definitions are evaluated. This is the default.

    • If set to undirected, edge and endpoint definitions are ignored.

  2. Using a directed property on an Edge.

    • If set to true, the endpoint level definitions are evaluated. This is the default.

    • If set to false, all endpoints are treated as undir, even if they state otherwise.

  3. Using the direction property on each Edge Endpoint.

    • The value set here is used if both graph {"edgeDefault": "directed" } and and edge "directed":true} are set (which are also the default values for both properties).

Table 1. Examples

Input

Result

graph edgeDefault

edge directed

endpoint direction

Result Endpoint Direction

Graph directed, Edge directed → Endpoint Direction

 — / directed

 — / true

 — / undir

undir

 — / directed

 — / true

in

in

 — / directed

 — / true

out

out

Graph directed, Edge undirected → undirected

 — / directed

false

 — / undir

undir

 — / directed

false

in

undir

 — / directed

false

out

undir

Graph undirected → undirected

undirected

 — / true

 — / undir

undir

undirected

 — / true

in

undir

undirected

 — / true

out

undir

undirected

false

 — / undir

undir

undirected

false

in

undir

undirected

false

out

undir

4.5. Identifiers

For Graph, Node, and Edge ids, the allowed JSON type is string or number(integer). A number is converted at import time to a string. E.g., the number 3 is convered to the string id "3". Floating-point numbers or negative numbers are not allowed.

If no identifier is given, an implementation can create any unique identifiers. Common strategies are:

  • use the array position as id, or

  • use an auto-incremented integer, or

  • use a UUID.

4.5.1. Identifier Scope

The identifiers for different elements have different scopes in which they must be unique.

Scope

Comment

Document

A document contains a set of top-level graphs. Graph ids, if used, MUST be unique per document.

Top-Level Graph

Node ids, Edge ids and nested Graph ids are unique per top-level graph. Nested graphs do not provide a new id scope.

Node

Port ids are only unique within their corresponding Node.

4.5.2. Streaming Assumptions

Connected JSON has some assumptions about the size and shape of the input data. These assumptions make it easier to build performant parsers.

Most entities are expected to be resonably small, so that they can be completely processed in memory. Some entities may occur a large number of times. As indicated by the Streaming notes below some property tables, the order of properties is also important. In general, small properties must come before the large properties (due to values with many child elements). Here is the conceptual grammar:

Elements with Large Number of Children
Figure 4. Elements with Large Number of Children

Note that attached JSON data is especially excluded from being overly large.

Appendix A: JSON Schema

The following schema has no support for aliases.

JSON Schema for Connected JSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/connected-json-v0.0.1.schema.json",
  "title": "Connected JSON Schema",
  "description": "A JSON Schema for the Connected JSON format (v0.0.1) used for exchanging connected data (graphs, networks). Allows arbitrary additional properties on all elements.",
  "version": "3.0.0",
  "type": "object",
  "properties": {
    "graph": {
      "$ref": "#/$defs/OneOrManyGraphs",
      "description": "A single graph object or an array of graph objects. Use this or 'graphs'."
    }
  },
  "required": [
    "graph"
  ],
  "additionalProperties": true,
  "$defs": {
    "Identifier": {
      "description": "A unique identifier, represented as a string or number.",
      "type": [ "string", "number" ]
    },
    "Label": {
      "description": "A display label, either as a single string or a multi-lingual object (e.g., {\"en\": \"Hello\", \"de\": \"Hallo\"}).",
      "oneOf": [
        {
          "type": "string"
        },
        {
          "type": "object",
          "patternProperties": {
            "^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$": {
              "type": "string"
            }
          },
          "additionalProperties": false,
          "minProperties": 1
        }
      ]
    },
    "OneOrManyGraphs": {
      "description": "Allows either a single Graph object or an array of Graph objects.",
      "oneOf": [
        {
          "$ref": "#/$defs/Graph"
        },
        {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Graph"
          },
          "minItems": 1
        }
      ]
    },
    "Nodes": {
      "description": "Allows either a single Node object or an array of Node objects.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/Node"
      }
    },
    "Edges": {
      "description": "Allows either a single Edge object or an array of Edge objects.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/Edge"
      }
    },
    "MaybeArrayOfIdentifiers": {
      "description": "Allows a single identifier (string/number) or an array of identifiers.",
      "oneOf": [
        {
          "$ref": "#/$defs/Identifier"
        },
        {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Identifier"
          },
          "minItems": 1
        }
      ]
    },
    "Graph": {
      "description": "Represents a graph containing nodes and edges.",
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Identifier",
          "description": "Unique identifier for the graph. Required."
        },
        "label": {
          "$ref": "#/$defs/Label",
          "description": "Optional display label for the graph."
        },
        "edgeDefault": {
          "type": "string",
          "enum": [
            "directed",
            "undirected"
          ],
          "description": "Optional. Defines default directedness for edges. If 'undirected', edges default to directed:false. Otherwise defaults to directed:true."
        },
        "nodes": {
          "$ref": "#/$defs/Nodes",
          "description": "Optional. Contains the nodes of the graph. Can be a single node object or an array."
        },
        "edges": {
          "$ref": "#/$defs/Edges",
          "description": "Optional. Contains the edges of the graph. Can be a single edge object or an array."
        }
      },
      "required": [
        "id"
      ],
      "additionalProperties": true
    },
    "Node": {
      "description": "Represents a node within a graph.",
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Identifier",
          "description": "Unique identifier for the node within its graph. Required."
        },
        "label": {
          "$ref": "#/$defs/Label",
          "description": "Optional display label for the node."
        },
        "name": {
          "$ref": "#/$defs/Label",
          "description": "Alias for 'label'. Optional."
        },
        "ports": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Port"
          },
          "description": "Optional array of ports attached to this node.",
          "minItems": 0
        }
      },
      "required": [
        "id"
      ],
      "additionalProperties": true
    },
    "Port": {
      "description": "Represents a connection point on a Node, can be nested.",
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "Identifier for the port, unique within its parent node or port. Required."
        },
        "label": {
          "$ref": "#/$defs/Label",
          "description": "Optional display label for the port."
        },
        "ports": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Port"
          },
          "description": "Optional array of sub-ports attached to this port.",
          "minItems": 0
        }
      },
      "required": [
        "id"
      ],
      "additionalProperties": true
    },
    "Edge": {
      "description": "Represents a connection (edge) between nodes or ports. Can use shortcut syntax (source/target) or canonical endpoint syntax.",
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/Identifier",
          "description": "Optional unique identifier for the edge."
        },
        "label": {
          "$ref": "#/$defs/Label",
          "description": "Optional display label for the edge."
        },
        "type": {
          "type": "string",
          "description": "Optional kind of link (text label, node id, or URI). Defaults to 'related'."
        },
        "directed": {
          "type": "boolean",
          "description": "Default: true. If false, all endpoints are treated as 'undir', overriding explicit directions and graph edgeDefault."
        },
        "endpoints": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Endpoint"
          },
          "description": "Canonical way to define edge connections using endpoint objects.",
          "minItems": 1
        },
        "source": {
          "$ref": "#/$defs/MaybeArrayOfIdentifiers",
          "description": "Shortcut syntax (with target): Node ID(s) interpreted as incoming ('in')."
        },
        "target": {
          "$ref": "#/$defs/MaybeArrayOfIdentifiers",
          "description": "Shortcut syntax (with source): Node ID(s) interpreted as outgoing ('out')."
        }
      },
      "oneOf": [
        {
          "description": "Canonical endpoint syntax requires 'endpoints'",
          "anyOf": [
            {
              "required": [
                "endpoints"
              ]
            }
          ]
        },
        {
          "description": "Shortcut syntax requires at least one source AND at least one target.",
          "allOf": [
            {
              "anyOf": [
                {
                  "required": [
                    "source"
                  ]
                }
              ]
            },
            {
              "anyOf": [
                {
                  "required": [
                    "target"
                  ]
                }
              ]
            }
          ]
        }
      ],
      "additionalProperties": true
    },
    "Endpoint": {
      "description": "Defines one end of an edge, connecting to a node or port with a specific direction.",
      "type": "object",
      "properties": {
        "node": {
          "$ref": "#/$defs/Identifier",
          "description": "Node ID this endpoint connects to. Required."
        },
        "port": {
          "$ref": "#/$defs/Identifier",
          "description": "Optional Port ID within the target node this endpoint connects to."
        },
        "direction": {
          "type": "string",
          "enum": [
            "in",
            "out",
            "undir"
          ],
          "description": "Optional direction relative to the edge ('in', 'out', 'undir'). Default 'undir'."
        },
        "type": {
          "type": "string",
          "description": "Optional type of relation from the edge to this endpoint node."
        },
        "typeUri": {
          "type": "string",
          "format": "uri",
          "description": "Optional URI representing the type of relation. Takes precedence over 'type' if both are given."
        }
      },
      "required": [
        "node"
      ],
      "additionalProperties": true
    }
  }
}

Appendix B: Reserved Property Names

The following property names are used by Connected JSON in certain places.

Property

Usage

baseUri

Graph base URI for RDF interpretation

directed

Edge directedness

direction

Edge Endpoint direction (in/out/undir)

edgeDefault

Graph default edge direction

edges

Graph edges

endpoints

Edge endpoints

graph

Node nested graph, Edge nested graph

id

Node id, Edge id, Graph id

label

Node, Edge, Graph

node

Edge Endpoint referenced node id

nodes

Graph nodes

port

Edge Endpoint referenced port id

ports

Node ports

source

Edge

target

Edge

type

Edge, Edge Endpoint

typeNode

Edge, Edge Endpoint

typeUri

Edge, Edge Endpoint

The following property names are merely suggested.

description

Suggested for Node, Edge, Graph, Port, Edge Endpoint description


1. The compacted form looks much cleaner, but prevents attaching metadata. Multi-language labels are rarely hand-authored and the array form is self-explanatory.