Skip to content

Create JSON Schema to further define and constrain the spec. #10

@mwadams

Description

@mwadams

I would suggest creating a JSON Schema to define the spec.

Here's a first pass at that.

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://github.com/obsidianmd/jsoncanvas/canvas.json",
    "title": "An open file format for infinite canvas data.",
    "description": "Infinite canvas tools are a way to view and organize information spatially, like a digital whiteboard. Infinite canvases encourage freedom and exploration, and have become a popular interface pattern across many apps.\nThe JSON Canvas format was created to provide longevity, readability, interoperability, and extensibility to data created with infinite canvas apps. The format is designed to be easy to parse and give users ownership over their data. JSON Canvas files use the .canvas extension.\nJSON Canvas was originally created for Obsidian. JSON Canvas can be implemented freely as an import, export, and storage format for any app or tool. This site, and all the resources associated with JSON Canvas are open source under the MIT license.",
    "type": "object",
    "properties": {
        "nodes": { "title": "An optional array of nodes.", "$ref": "#/$defs/nodeCollection"},
        "edges": { "title": "An optional array of edges.", "$ref": "#/$defs/edgeCollection"}
    },

    "$defs": {
        "nodeCollection": {
            "title": "An array of nodes.",
            "type": "array",
            "items": { "$ref": "#/$defs/node"}
        },
        "edgeCollection": {
            "title": "An array of edges.",
            "type": "array",
            "items": { "$ref": "#/$defs/edge"}
        },

        "node": {
            "title": "A node.",
            "description": "Nodes are objects within the canvas. Nodes may be text, files, links, or groups.",
            "oneOf": [
                { "$ref": "#/$defs/textNode" },
                { "$ref": "#/$defs/fileNode" },
                { "$ref": "#/$defs/linkNode" },
                { "$ref": "#/$defs/groupNode" }
            ]
        },

        "genericNode": {
            "title": "A generic node.",
            "type": "object",
            "required": ["id", "type", "x", "y", "width", "height"],
            "properties": {
                "id": {
                    "title": "A unique identifier for the node.",
                    "type": "string"
                },
                "type": { "title": "The node type (a discriminator).", "$ref": "#/$defs/nodeType"},
                "x": { "title": "The x position of the node in pixels.", "$ref": "#/$defs/coordinate" },
                "y": { "title": "The y position of the node in pixels.", "$ref": "#/$defs/coordinate" },
                "width": { "title": "The width of the node in pixels.", "$ref": "#/$defs/dimension" },
                "height": { "title": "The height of the node in pixels.", "$ref": "#/$defs/dimension" },
                "color": { "title": "The color of the node.", "$ref": "#/$defs/color" }
            }
        },

        "textNode": {
            "title": "Text type nodes store text.",
            "$ref": "#/$defs/genericNode",
            "required": ["type", "text"],
            "properties": {
                "type": { "$ref": "#/$defs/textType" },
                "text": { "title": "Text in plain text with Markdown syntax.", "type": "string"}
            }
        },

        "fileNode": {
            "title": "File type nodes reference other files or attachments, such as images, videos, etc.",
            "$ref": "#/$defs/genericNode",
            "required": ["type", "file"],
            "properties": {
                "type": { "$ref": "#/$defs/fileType" },
                "file": { "title": "The path to the file within the system.", "type": "string"},
                "subpath": { "title": "A subpath that may link to a heading or a block. Always starts with #.", "$ref": "#/$defs/subpathReference"}
            }
        },

        "linkNode": {
            "title": "Link type nodes reference a URL.",
            "$ref": "#/$defs/genericNode",
            "required": ["type", "url"],
            "properties": {
                "type": { "$ref": "#/$defs/linkType" },
                "url": { "title": "The URL referenced by the link", "type": "string", "format": "iri" }
            }
        },

        "groupNode": {
            "title": "Group type nodes are used as a visual container for nodes within it.",
            "$ref": "#/$defs/genericNode",
            "required": ["type"],
            "properties": {
                "type": { "$ref": "#/$defs/groupType" },
                "label": { "title": "A text label for the group.", "type": "string"},
                "background": { "title": "The path to the background image.", "type": "string"},
                "backgroundStyle": { "title": "The rendering style of the background image.", "$ref": "#/$defs/backgroundStyles"}
            }
        },

        "nodeType": {
            "type": "string",
            "enum": ["text", "file", "link", "group"]
        },

        "textType": {
            "title": "The type of a text node.",
            "const": "text"
        },

        "fileType": {
            "title": "The type of a file node.",
            "const": "file"
        },

        "linkType": {
            "title": "The type of a link node.",
            "const": "link"
        },

        "groupType": {
            "title": "The type of a group node.",
            "const": "text"
        },

        "edge": {
            "title": "Edges are lines that connect one node to another.",
            "type": "object",
            "required": ["id", "fromNode", "toNode"],
            "properties": {
                "id": {
                    "title": "A unique identifier for the edge.",
                    "type": "string"
                },
                "fromNode": {
                    "title": "The node id where the connection starts.",
                    "type": "string"
                },
                "fromSide": {
                    "title": "The side where this edge starts.",
                    "$ref": "#/$defs/side"
                },
                "fromEnd": {
                    "title": "The shape of the endpoint at the edge start",
                    "$ref": "#/$defs/endpointShape"
                },
                "toNode": {
                    "title": "The node id where the connection ends.",
                    "type": "string"
                },
                "toSide": {
                    "title": "The side where this edge ends.",
                    "$ref": "#/$defs/side"
                },
                "toEnd": {
                    "title": "The shape of the endpoint at the edge end",
                    "$ref": "#/$defs/endpointShape"
                },
                "color": {
                    "title": "The color of the line.",
                    "$ref": "#/$defs/color"
                },
                "label": {
                    "title": "The text label for the edge.",
                    "type": "string"
                }
            }
        },

        "side": {
            "title": "The side of a node.",
            "type": "string",
            "enum": ["top", "right", "bottom", "left"]
        },

        "endpointShape": {
            "title": "The shape of the endpoint of an edge.",
            "type": "string",
            "enum": ["none", "arrow"]
        },

        "coordinate": {
            "title": "A co-ordinate (x or y) in pixels.",
            "type": "integer"
        },

        "dimension": {
            "title": "A dimension (width or height) in pixels.",
            "type": "integer",
            "minimum": 1
        },

        "color": {
            "title": "The color type is used to encode color data for nodes and edges",
            "oneOf": [
                { "$ref": "#/$defs/hexColor" },
                { "$ref": "#/$defs/presetColor" }
            ]
        },

        "hexColor": {
            "title": "A color in hexadecimal format.",
            "type": "string",
            "pattern": "^#(?:[0-9a-fA-F]{3}){1,2}$"
        },

        "presetColor": {
            "title": "A preset color.",
            "description": "Six preset colors exist, mapped to the following numbers:\n1 red\n2 orange\n3 yellow\n4 green\n5 cyan\n6 purple",
            "type": "integer",
            "enum": [1, 2, 3, 4, 5, 6]
        },

        "subpathReference": {
            "title": "A subpath that may link to a heading or a block. Always starts with #.",
            "type": "string",
            "pattern": "#(?:.*)"
        },

        "backgroundStyles": {
            "title": "The rendering style of a background image.",
            "description": "Options are:\ncover - fills the entire width and height of the node.\nratio - maintains the aspect ratio of the background image.\nrepeat - repeats the image as a pattern in both x/y directions.",
            "type": "string",
            "enum": ["cover", "ratio", "repeat"]
        }
    }
}

From this we can generate object models to validate and operate over the files in a variety of different languages.

For example, I attach code generated by Corvus.JsonSchema to work with the object model in C#/dotnet.

Corvus.JsonSchema.Generated.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions