[VRChat] DataDictionary and JSON: Reading Settings into Your Lights

Created: 2026-09-08Last updated: 2026-09-09

DataList and DataDictionary for when arrays aren't enough. Load light settings written in JSON, look values up by name, and apply them. Also covers checking types on the way out.

You can gather three lights into an array now. But you'll want settings like this.

"The first light is named 'entrance,' its brightness is 2, and its color is warm." Name, brightness, and color all differ per light. Three parallel arrays would work, and you'd never notice when they fell out of alignment.

This article covers containers you can look up by name, and a way to bundle settings into a single piece of text.

Reading external settings data and changing three lamps' brightness

What You'll Learn

  • Where arrays stop being enough
  • The difference between DataList and DataDictionary
  • The steps for reading JSON and getting values out
  • Writing it so a failed load doesn't break anything

Start from having tried an introduction to arrays.

Sponsored


Where arrays stop being enough

An array is a container that "lines up things of the same kind by number." It falls short in two situations.

First, when you want to grow or shrink at runtime. An array's element count must be decided up front and can't grow later. That's a problem for something like "record each person as they join."

Second, when you want to look things up by name rather than number. Remembering which index "the entrance light's brightness" is at gets painful.

VRChat provides dedicated containers for both.

The settings data's structure: name and value pairs
ContainerWhat it doesWhen to use it
ArrayLook up by number. Fixed countThe count is known
DataListLook up by number. Grows and shrinks at runtimeRecords accumulate
DataDictionaryLook up by nameYou want named settings

Ordinary C#'s List and Dictionary aren't available in UdonSharp. Use these two from VRChat.

There's one more term to learn: DataToken. Every value in these containers sits inside a wrapper called a DataToken. Whether the contents are a number or text gets checked on the way out.

The reason for wrapping is so one container can hold different kinds of thing. The same settings entry can carry a name (text) and a brightness (number) side by side.

Sponsored

Hands-On: Read JSON settings into your lights

Bundle three lights' settings into one string and apply it.

1. Place three lights

Place three Point Lights in a scene with a floor. Name them Lamp0, Lamp1, Lamp2 at (-1.5, 2.2, 1), (0, 2.2, 1), (1.5, 2.2, 1). Mode is Realtime.

Place one button as the loading trigger. Put a Cube named LoadButton at (0, 1, -1).

The lights and button placed in the scene

2. Write settings in JSON

JSON is a convention for expressing data as text. Names and values are joined with : and grouped with { }.

Here's what we'll use.

{
  "lamps": [
    { "name": "entrance", "intensity": 2.5 },
    { "name": "center",   "intensity": 1.0 },
    { "name": "window",   "intensity": 0.4 }
  ]
}

Under the name lamps sit three groups, each holding a name and an intensity.

In this shape, adding settings doesn't break the structure. Adding a fourth light means adding one line.

3. Write the code

In Assets/Scripts, choose "Create → U# Script" and make LampConfigLoader.

using UdonSharp;
using UnityEngine;
using VRC.SDK3.Data;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class LampConfigLoader : UdonSharpBehaviour
{
    [SerializeField] private Light[] lamps;

    [TextArea(4, 10)]
    [SerializeField] private string configJson =
        "{\"lamps\":[{\"name\":\"entrance\",\"intensity\":2.5}," +
        "{\"name\":\"center\",\"intensity\":1.0}," +
        "{\"name\":\"window\",\"intensity\":0.4}]}";

    public override void Interact()
    {
        LoadConfig();
    }

    private void LoadConfig()
    {
        // 1. Parse the text into data
        if (!VRCJson.TryDeserializeFromJson(configJson, out DataToken root))
        {
            Debug.LogWarning("[LampConfig] couldn't read the JSON: " + root.ToString());
            return;   // On failure, leave the current settings alone
        }

        // 2. Confirm the outermost level is a dictionary
        if (root.TokenType != TokenType.DataDictionary) return;
        DataDictionary rootDict = root.DataDictionary;

        // 3. Get the list out by the name "lamps"
        if (!rootDict.TryGetValue("lamps", TokenType.DataList, out DataToken listToken)) return;
        DataList list = listToken.DataList;

        // 4. Read one at a time and apply to the matching light
        int count = Mathf.Min(list.Count, lamps.Length);
        for (int i = 0; i < count; i++)
        {
            if (!list.TryGetValue(i, TokenType.DataDictionary, out DataToken itemToken)) continue;
            DataDictionary item = itemToken.DataDictionary;

            if (!item.TryGetValue("intensity", TokenType.Double, out DataToken value)) continue;
            if (lamps[i] == null) continue;

            lamps[i].intensity = (float)value.Double;
        }

        Debug.Log("[LampConfig] applied " + count + " entries");
    }
}

It looks long, and it's doing four things.

  1. Parse the text into data (TryDeserializeFromJson)
  2. Confirm the outermost shape
  3. Get the contents out by name (TryGetValue)
  4. Apply to the lights one at a time

Names starting with Try line up here. That means "returns whether it succeeded." Failure doesn't halt anything; it just returns false, which you catch with if (!...) return;.

Doing nothing on failure is the key point. A settings file that can't be read shouldn't turn off every light. Leaving the previous state is the safer choice for a world.

4. Assign it and confirm

Add a Udon Behaviour to LoadButton and set LampConfigLoader. Set Lamps' size to 3 and assign Lamp0 onward.

Press Play and try it.

ActionExpected result
Press the buttonThe three brightnesses become 2.5, 1.0, 0.4. Console shows applied 3 entries
Change 2.5 to 5 in the Inspector and pressOnly the first gets brighter
Delete one } from the JSON and presscouldn't read the JSON appears and the lights don't change

The third check matters. Handing it broken settings doesn't break the world. It reports the error and keeps running.

Check the type before taking it out

The TokenType.Double in TryGetValue is there to confirm the kind of contents.

Look up by name and take out with a specified type; a mismatched type won't come out

A DataToken is "a wrapper with something in it," so you can't tell what's inside until you open it. Trying to pull a number out of a wrapper holding text fails.

Taking it out with a type specified means a mismatch just returns false. Without specifying, you'd use an unexpected value as-is.

That check earns its keep especially with data from outside. When someone edits the settings, what should be a number turning into text is entirely plausible.

With checks in place, broken input gets rejected safely

One more thing to know: JSON numbers arrive as Double. Both 2.5 and 1 are treated as decimals, without distinction. Unity's intensity is a float, so we convert with (float).

Common Pitfalls

  • VRCJson isn't foundusing VRC.SDK3.Data; is missing
  • TryGetValue always fails → Check the name's spelling and the type specified. Numbers are TokenType.Double
  • It says it can't read the JSON → Check the counts of braces and commas. Double quotes inside a string are written \"
  • The lights don't change → Are Lamps' elements empty? Does the JSON's entry count match the number of lights?
Sponsored

Bonus: Good to Know Up Front

  • DataList grows at runtime: list.Add(...) appends and list.Count gives the count. Suits recording visitor actions in order
  • You can write JSON out too: VRCJson.TrySerializeToJson turns a DataDictionary back into a string. Useful for saving and sending
  • It can be loaded from an external URL: This article uses a string written in code, and you can also read a file from the web. Covered in updating a board from external text
  • Deep problems surface late: Even when loading succeeds, damage further in only fails when you get to it. Check TryGetValue's return value every time
  • They can't be synced directly: DataList and DataDictionary can't be shared with [UdonSynced]. To share them, convert to a string and send that

Summary

When arrays aren't enough, use a container you can look up by name.

  • DataList looks up by number and grows and shrinks; DataDictionary looks up by name
  • Contents are wrapped in DataTokens. Check the type on the way out
  • JSON is a way of bundling settings into a single piece of text
  • Do nothing when loading fails. Leaving the previous state is safer

The question to ask while designing is: "Do I pick this value by number, or by name?" By name means DataDictionary.

When you want to read settings from an external file, go to updating a board from external text. To save settings per person, go to saving volume with PlayerData.

VRChat Notes in this section63