Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
mwadams opened this issue Mar 12, 2024 · 7 comments · May be fixed by #19
Open

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

mwadams opened this issue Mar 12, 2024 · 7 comments · May be fixed by #19

Comments

@mwadams
Copy link

mwadams commented Mar 12, 2024

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

@chainlist
Copy link

chainlist commented Mar 12, 2024

This is a duplicate.

You can see my attempt made yesterday. Already done an MR. Maybe there is a few things to tweak like adding description though.

I think we can either work from yours or mine.

@mwadams
Copy link
Author

mwadams commented Mar 12, 2024

The interesting thing about this approach with the documentation in the schema like this is that we could generate the human-readable text from the schema.

@Mearman
Copy link

Mearman commented Mar 12, 2024

I've been diving deep into JSON Schema and OpenAPI specs recently. I think building the JSON Schema from TypeScript source is good for maintainability.
If there is an appetite for it I can transform this JSON to TS and set up the pipeline for programmatically generating the JSON Schema?
also relates to #4

@mwadams
Copy link
Author

mwadams commented Mar 12, 2024

I've been diving deep into JSON Schema and OpenAPI specs recently. I think building the JSON Schema from TypeScript source is good for maintainability. If there is an appetite for it I can transform this JSON to TS and set up the pipeline for programmatically generating the JSON Schema? also relates to #4

Interesting idea!

Couple of questions:

  • Which tool do you intend to use for the generation?
  • Does it support draft2020-12 (which is the current JSON Schema draft)
  • What are advantages over creating/editing JSON Schema documents (how do you validate that it is producing valid/useful schema?

@Mearman
Copy link

Mearman commented Mar 12, 2024

I've experimented with pretty much every tool I can find. So it's dealer's choice. I've found Draft-07 to be a lot more widely supported still, but I have enough reliable tooling to support 2020-12. What are @kepano and @ericaxu's thoughts?

@mwadams
Copy link
Author

mwadams commented Mar 13, 2024

Over in json-schema land we are warmly encouraging people to adopt 2020-12. Most draft7 specs translate trivially and you will be well positioned for vNext. There is now plenty of high quality tooling for most languages.

@Mearman
Copy link

Mearman commented Mar 13, 2024

@mwadams for me the one downside is (as of writing) lack of support in VSCode for 2020-12

Mearman added a commit to ExaDev/breadboard-examples that referenced this issue Mar 13, 2024
Mearman added a commit to Mearman/jsoncanvas that referenced this issue Mar 13, 2024
Mearman added a commit to Mearman/jsoncanvas that referenced this issue Mar 14, 2024
Mearman added a commit to Mearman/jsoncanvas that referenced this issue Mar 14, 2024
…erfaces. Define properties and types for nodes and edges. Include descriptions for clarity. Create TypeScript interfaces for JsonCanvas, CanvasNode, TextNode, FileNode, LinkNode, GroupNode, AllNodes, and Edge with detailed attributes and types.

alternative proposal for obsidianmd#10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants