JSON Config in TouchDesigner

Downlad via OLIB

If you followed my talk at the 2023 TouchDesigner Event in Berlin, you might already be familiar with the topic of this post: the JSON Config Component. This component allows you to generate a JSON file and reference its values inside TouchDesigner. This way, you can create, define, and edit the behavior of your TouchDesigner project without ever having to open the patch itself. It’s particularly useful for multi-deployments, where you only need to create and maintain one project, yet can deploy it in several locations.

To achieve this, the JSON Config Component offers two methods:

1. Using an Environment Variable

By defining an environment variable (ENV-Variable) and enabling this feature in the component, the generated JSON file will be appended with the environment name. Or, if the file already exists, it will be loaded accordingly. A classic example would be API endpoints and keys. Let’s say you have both a development and production environment. You can even go further by having different endpoints for different computers, defining all these values beforehand, and only updating the environment variables on the target computers. Then, restart TouchDesigner to load the updated settings.

We used this approach in 2021 during a trade show with five different screens. All screens had the same CI and existed in a shared 3D space. All five screens were defined in the same file, and depending on the environment, we could enable or disable project elements. Managing the file from a central server allowed us to distribute changes via a local Git filesystem, enabling every server to fetch the latest version from a single source of truth.

2. Using Local Files

The first approach requires all settings and information to be part of a repository. This means that everyone with access to the repository also has access to potential API secrets or similar sensitive data, which is not ideal. Instead of pushing the configuration to all PCs, you can manually distribute the configs using .gitignore to keep them out of the repository. While this makes deployment and interchange more difficult, it enhances security.

Why Not Just a JSON File or Environment Variables?

The advantage of the JSON Config Component, though it may seem more complex than its predecessor, is that the schema of the config is predefined in Python itself. You always have to update the callback instead of just dumping a value. However, this allows you to:

  • Add a parser, type-checking, and default values.
  • Generate a default config if none exists, thanks to default values. This means incomplete configs won’t break a project.
  • Generate a JSON schema that is detected and used by Visual Studio Code, allowing for code completion and comments on entries.
  • Generate the schema dynamically based on the current state of the project (examples provided later).

How to Access the Data

To access the data, I did something that might be considered bad practice, but it works. The data is stored in a structure called PointDict. In short, you can access the data either via dot notation or via the JSON-described structure (as a dict or list). The actual value is accessed through either the Data (read-only) or the Dependency attribute. This dynamic behavior is made possible because the data is „hidden“ behind the Data attribute of the jsonConfigCOMP.

Data-Json: {„Foo“ : {„Bar“ : 12} }
Acces via op(„jsonConfig“).Data.Foo.Bar.Data # Evaluates to 12

Wait, what’s the thing with Dependency? You can actually bind to the Dependency object and use the Save() method of the jsonConfig to store the current state back into the JSON file.

Writing the Schema

Now, let’s dive into writing the schema. The schema is defined via a callback. The definition module is passed as an argument, allowing us to create the schema as a return value. The return value is initially a dictionary, but everything else has to be defined using the config methods from the configModule. Here are the key methods:

  • ConfigValue: The final value referenced, with the following arguments:
    • default: The default value, if the value is not defined or the validator/typechecker fails.
    • validator: A function that validates the value and returns a boolean.
    • parser: If valid, transforms the input and returns the new value (this might go before validation).
    • typecheck: A type or tuple of types that defines the valid value type. If not defined, it takes the type of the default value.
    • comment: A comment used for the JSON schema.
  • CollectionDict: Takes a dictionary and makes its members accessible. It can also accept arguments.
  • CollectionList: This has three arguments:
    • items: Default items used to populate the list.
    • default_member: A config item that describes the schema of the list items.
  • NamedList: Similar to a dictionary, but instead of a fixed definition like CollectionDict, it defines a default_member and allows an arbitrary number of items. It takes the same arguments as CollectionList.
  • EnumValue: Only allows predefined values, with the same arguments as ConfigValue plus:
    • allowedValues: A list of allowed values.

Reusing Items

If you want to reuse configuration items, such as RGB color definitions, you can create reusable items like this:

rgbColor = configModule.CollectionDict({
  "R" : configModule.ConfigValue(default=1.0, comment="Value between 0 and 1"),
  "G" : configModule.ConfigValue(default=1.0, comment="Value between 0 and 1"),
  "B" : configModule.ConfigValue(default=1.0, comment="Value between 0 and 1"),
 })

You can now reuse it by calling the item

return {
    "Color1" : rgbColor(),
    "Color2" : rgbColor()
}

Ok, Cool, Now What?

Check the demo file to see what’s possible. Here’s a quick list of examples:

  • Fetching data from a Web API.
  • Generating an ENUM value from a MenuParameter to select audio devices.
  • Autosaving values when they change.
  • Autogenerating the schema based on tags and parameters.
  • Creating operators based on the jsonConfig without needing to transform the data