0

I am in the process of implementing a visual programming language similar to Blender's node editor or Unreal Engine Blueprints. It will be implemented as a SPA that will communicate with a JSON based REST API.

I am trying to represent a graph similar to this in JSON:

+------+
|Number|
| Value|-----+  +----------+
+------+     |  |Sum       |
             |  |    Result|
             +--|A         |
+------+     +--|B         |
|Number|     |  +----------+
| Value|-----+
+------+

The best way I can think of doing this is by having two separate lists of JSON objects, one to hold a list of "node definitions" and another representing the graph that will reference the list of node definitions.

Here is the above graph represented in JSON using this approach.

Node definitions:

[
    {
        "name": "num",
        "title": "Number",
        "inputs": [],
        "outputs": [
            {
                "name": "val",
                "label": "Value"
            }
        ]
    },
    {
        "name": "sum",
        "title": "Sum",
        "inputs": [
            {
                "name": "a",
                "label": "A"
            },
            {
                "name": "b",
                "label": "B"
            }
        ],
        "outputs": [
            {
                "name": "res",
                "label": "Result"
            }
        ]
    }
]

Node graph:

{
    "nodes": [
        {
            "id": 0,
            "instanceName": "sum",
            "posX": 200,
            "posY": 85,
        },
        {
            "id": 1,
            "instanceName": "number",
            "posX": 10,
            "posY": 10,
        },
        {
            "id": 2,
            "instanceName": "number",
            "posX": 10,
            "posY": 200,
        }
    ]
    "edges": [
        {
            "source": {
                "nodeId": 1,
                "ioName": "value"
            },
            "target": {
                "nodeId": 0,
                "ioName": "a"
            }
        },
        {
            "source": {
                "nodeId": 2,
                "ioName": "value"
            },
            "target": {
                "nodeId": 0,
                "ioName": "b"
            }
        }
    ]
}

Here are a few potential issues I can see with this approach:

  • The list of node definitions could grow quite large. This could slow things down when trying to load a graph from the server as you would have to load both the list of node definitions and the node graph itself.
  • Maybe it would be a better idea to replace the string names for the node definitions and IO definitions with integer identifiers.
  • It might make more sense if the list of nodes in the node graph were an object with key-value pairs where the ID is the key, instead of an array.

Is this a good approach? Is there a better way to do this?

2
  • 1
    Writing a new language generally needs you to build a whole new ecosystem to support it. e.g. How would someone debug a 'program' and set breakpoints? how would they profile it to identify performance issues? how would they write automated tests? how would they split the program into modules? How would they add it to source control? How would they create a CI/CD pipeline? What about learning/training? Make sure you consider the reality of actually maintaining programs otherwise I'd recommend looking at using an existing language which already has these tools instead. Mar 31 at 17:00
  • Those are good points and I will be sure to keep them in mind when designing and building the language. I forgot to mention that I am not building a general-purpose programming language so for my specific use case some of those points are not important. Mar 31 at 17:50
0

Your main concern seems to be the API payload size, so I am going to address mostly that. In general:

  • Ensure data is normalized. Denormalization may be useful for increasing DB query performance, but not much for transferring JSON.

  • Avoid nested objects, give every type it's own table. Nested objects cannot be denormalized or requested separately.

    • If nested objects is fully owned by the outer, consider embedding it into the outer.
  • Only request what is required and cache what was received. Request the root objects and let the server to work out what else must be included.

  • You may be tempted to compress resulting JSON or use tricks like JSONH. Don't, gzip will do it for you.


Looking at your specific example:

  • Inputs and outputs are candidates for extraction. These also seems to be of same type, let's put them in the same table, for now.
  • Edge owns 2 objects, let's flatten it.

Also, I renamed few things and changed the format settings just for the readability and the screen size. Here is the result:

"parameters": [
    {"id": 1, "label": "Value"},
    {"id": 2, "label": "Result"},
    {"id": 3, "label": "A"},
    {"id": 4, "label": "B"},
],

// node definitions
"operators": [
    {"id": 1, "title": "Number", "in": [], "out": [1]},
    {"id": 2, "title": "Sum", "in": [3, 4], "out": [2]},
],

"nodes": [
    {"id": 1, "op": 2, "X": 200, "Y": 85},
    {"id": 2, "op": 2, "X": 10, "Y": 10},
    {"id": 3, "op": 1, "X": 10, "Y": 200},
],

"edges": [
    {"inNode": 2, "inParam": 1, "outNode": 1, "outParam": 3},
    {"inNode": 3, "inParam": 1, "outNode": 1, "outParam": 4},
],

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.