Skip to main content

Data Lists

Data Lists store Data Tokens by index, similarly to C# Lists. Most Data List functions are just wrappers for the underlying C# list, so the C# list documentation also applies if you are looking for more specific details.

Data Lists can be serialized to/from JSON strings using VRCJSON. This is the current recommended method of syncing Data Lists over the network.

If you are using UdonSharp, include the using VRC.SDK3.Data; directive to use data lists.

Properties

PropertyResult
CapacitySet or get the capacity of the list. See C# documentation for further details.
CountGet the number of elements in the list

Functions

FunctionInputOutputResult
AddDataTokenAdds a token at the end of the list
AddRangeDataListAdds the values of another Data List at the end of this Data List.
BinarySearchDataToken valueint indexUses a binary search algorithm to locate a specific element in the List by comparison. In order to perform a binary search, the list must be sorted. See C# documentation for further details on binary search.
BinarySearchDataToken value, int startIndex, int countint indexPerforms a binary search within the specified range, starting at the provided index and extending toward the end of the list by the provided count. Note that this function searches by comparison, and will compare dictionaries and lists by count rather than contents. This makes it unsuitable for finding a specific dictionary or list within a list.
ClearRemoves all values from this list.
ContainsDataToken valuebool resultReturns true if the Data List contains the specified value.
DeepCloneDataList resultClones the DataList into a new DataList that contains all the same values. This does do a deep clone, which means that it will recursively navigate inside each DataList or DataDictionary and copy their contents as well. However, it will not look inside other structures such as arrays, and those will still have the same reference to the original.
GetRangeint index, int countDataList outputCopies a segment of the DataList out to another DataList. Returns false if index or count were out of range.
IndexOfDataToken itemint indexSearches for the specified object and returns the zero-based index of the first occurrence within the entire DataList. If not found, returns -1.
IndexOfDataToken item, int startIndexint indexSearches for the specified object and returns the zero-based index of the first occurrence within the range of elements in the DataList that extends from the specified index to the last element. If not found, returns -1.
IndexOfDataToken item, int startIndex, int countint indexSearches for the specified object and returns the zero-based index of the first occurrence within the range of elements in the DataList that starts at the specified index and contains the specified number of elements. If not found, returns -1.
Insertint index, DataToken inputbool successInserts a token into the middle of the list. All entries above the specified index will be shifted up one. Returns false if index was out of range.
InsertRangeint index, DataList inputInserts a DataList into the middle of the DataList. All entries above the specified index will be shifted up. Returns false if index was out of range.
LastIndexOfDataToken itemint indexSearches for the specified object and returns the zero-based index of the last occurrence within the DataList. If not found, returns -1.
LastIndexOfDataToken item, int startIndexint indexSearches for the specified object and returns the zero-based index of the last occurrence within the range of elements in the DataList that extends from the first element to the specified index. If not found, returns -1.
LastIndexOfDataToken item, int startIndex, int countint indexSearches for the specified object and returns the zero-based index of the last occurrence within the range of elements in the DataList that contains the specified number of elements and ends at the specified index. If not found, returns -1.
RemoveDataToken valuebool successRemoves the first occurrence of the specified value. Returns true if a matching value was found, returns false if not.
RemoveAllDataToken valuebool successRemoves all occurrences of the specified value. Returns true if any matching values were found, returns false if not.
RemoveAtint indexRemoves the element at the specified index.
RemoveRangeint index, int countRemoves a range of elements from the list.
ReverseReverses the order of all elements in the list.
Reverseint index, int countReverses the order of elements within the range specified, starting at the provided index and extending toward the end of the list by the provided count.
SetValueint index, DataToken inputSets a DataToken at the specified index.
ShallowCloneDataList resultClones the DataList into a new DataList that contains all the same values. This does not do a deep clone, which means that if the DataList contains references to other Data Containers, those will still be the same reference.
SortSorts all the elements in the list. If all elements are the same type, then they will be sorted by that type's native comparison operation. If a DataList contains multiple different types but those types are all numbers, then it will sort them with a numeric conversion. If a DataList contains multiple different non-numeric types, then it will sort them in this order: Null, Number, String, DataList, DataDictionary, Reference
Sortint index, int countPerforms the same operation as Sort but only within the range specified, starting at the provided index and extending toward the end of the list by the provided count.
ToArrayDataToken[] outputConverts the DataList into a DataToken array
TrimExcessSets the capacity to the actual number of elements in the DataList, if that number is less than a threshold value.
TryGetValueint indexDataToken outputGets a token from the specified index and puts it in the out DataToken. Returns true if successful.
TryGetValueint index, TokenType expectedbool success, DataToken outputGets a token from the specified index and puts it in the out DataToken. Returns true if successful. This version of TryGetValue includes a TokenType, which means it will do an automatic type check for you. If the type does not match, it will return false with DataError.TypeMismatch

Note that calling functions which affect or look at all values such as Contains, IndexOf, and LastIndexOf on a Data List generated from Json will parse all top-level values that have not already been parsed, which may be expensive with many values. Once they are parsed, future operations will be cheaper.

Getting a value from a DataList

There are several different ways to get a value out of a DataList. Each one has it's own use case, and it is up to you to choose which one you want to use.

TryGetValue

If you want to get a value out of a list safely, it is recommended to use TryGetValue. This is a function that returns true or false depending on whether or not getting the value was successful. It is intended to put this inside of the conditions for an if or a branch so that it is clear what happens when it succeeds and what happens when it fails.

Example of TryGetValue
if (list.TryGetValue(0, out DataToken value))
{
Debug.Log($"Success! {value}");
}
else
{
Debug.Log("Failed! {value}");
}

If this does fail, the DataToken you receive is still valid, but rather than containing your data it will contain an error.

This method is good for when you want to get some value from some location, but you don't care what it is exactly.

As this function does not have a type check built in, you should pair this function with some form of type checking, whether that be an if, branch, or switch. If you only care about one specific type, then it is recommended to just use the version of TryGetValue with TokenType, which does an automatic type check.

TryGetValue with TokenType

If you want to get a value from a list and you don't know what type it could be, it is important to do type checks. You could do that yourself in your own code, but that can get messy. Instead, you can use the version of TryGetValue that includes a TokenType. When you do this, it indicates that you only want to retrieve the value if it is the type you expect. Otherwise, it returns false and that can be handled gracefully.

This method is good for when you want to get a specific value from a specific location, but the data is coming from an outside source so you are not confident that the source has the right data.

Example of TryGetValue with TokenType
// You could do it this way, but it's a bit ugly
if (list.TryGetValue(0, out DataToken value)) {
if (value.TokenType == TokenType.DataDictionary)
{
Debug.Log($"Success! Matching dictionary has {value.DataDictionary.Count} items");
}
}

// This approach has a type check built in! It's functionally the same, but streamlined.
if (list.TryGetValue(0, TokenType.DataDictionary, out value)) {
Debug.Log($"Success! Matching dictionary has {value.DataDictionary.Count} items");
}

Shorthand Bracket syntax

You can also set and get items from a DataList using bracket syntax such as list[5] = "value"; in UdonSharp or DataList Get Item node in Udon graph. This method is smaller and easier to use. However, be aware that this is not completely safe and may halt your udonbehaviour if you attempt to perform an invalid operation. You should only use this if you have complete control over the data and can guarantee that it exists and is the type you expect. Otherwise, it is recommended to use some form of TryGetValue.

Example of Shorthand Bracket syntax
list[0] = 5;
list[1] = 10;

// This makes the assumption that index 0 and 1 will always contain integers.
// This is a safe assumption to make since we set them just above in a controlled environment.
// If the data is coming from an external source, we shouldn't make these assumptions!
int sum = list[0].Int + list[1].Int;

Initializing A Data List

In Udonsharp, Data Lists can be initialized in private variables. This allows you to have a pre-existing set of data that is defined before your code runs. This also supports nested dictionaries and anything else that DataTokens support. Here is an example of how you should use this syntax:

Example of initializing a Data List
private DataList _groceries = new DataList()
{
"Bananas",
"Grapes",
"Milk",
"Soda",
"Turkey",
"Ham",
"Roast Beef"
}

At the moment, Udonsharp does not support initializers of this type inside a function. This would be a feature request for Udonsharp.

At the moment, Unity does not serialize DataLists, which means that this is not recommended for serialized public variables. It should be used for private or [NonSerialized] public variables only. This is an addition to the feature that we are still working on.

Syncing a Data List with other players over the network

Data Lists cannot be directly synced. However, they can be serialized to/from JSON strings using VRCJson. This is the current recommended method of syncing Data Lists with UdonSync.

One way to do this is to use OnPreSerialization and OnDeserialization to Serialize and Deserialize the json string. Using this method, you won't need to worry about the serialization within the rest of your code, and you can simply set values and forget.

Example of syncing a Data List with other players over the network
[UdonSynced]
private string _json;
private DataList _list;

public override void OnPreSerialization()
{
if (VRCJson.TrySerializeToJson(_list, JsonExportType.Minify, out DataToken result))
{
_json = result.String;
}
else
{
Debug.LogError(result.ToString());
}
}

public override void OnDeserialization()
{
if(VRCJson.TryDeserializeFromJson(_json, out DataToken result))
{
_list = result.DataList;
}
else
{
Debug.LogError(result.ToString());
}
}

FAQ

Why not have a ToArray for each type?

It would be desirable to have a ToArray method for each data type. However, this is not currently feasible due to the lack of support for generics within Udon. While it is technically possible to create individual methods such as ToStringArray, ToFloatArray, ToDoubleArray, and others, this approach would result in excessive bloat to cover every possible type. Additionally, these methods would become deprecated once Udon 2 introduces support for generics. Furthermore, the basic ToArray methods would not provide significant added value. The real advantage would arise from the ability to execute ToArray on an object type, such as ToArray(typeof(Collider)), which would eliminate the need for casting. Nevertheless, supporting a ToArray of all possible objects is not practical, and supporting a ToArray of object specifically would be even worse than working with tokens.

Despite the fact that retrieving values from DataTokens can be a somewhat cumbersome process, they are specifically designed for this purpose and have several utilities to assist with this task.

Arrays are similar, what's the difference?

Arrays are a similar structure, used for storing lots of values in a sequential order, accessed by index. They are also very simple, and highly efficient at doing exactly that and nothing else. DataLists are a more complex type that can do a lot more. For example, an array has to be initialized with a specific length when it is first created, and you cannot add more items to it unless you create a new array to replace it. But don't be fooled - there are still good reasons to use an array instead of a list.

When should I use a DataList instead of an array?

Picking a DataList over an array should be done when there is a particular feature you need, so not everything needs to be switched.

  • When you want to add new items or remove items to the container dynamically. Arrays cannot do this.
  • When you want a single container to contain multiple different types at the same time. Arrays cannot do this.
  • When you want a container to contain containers arbitrarily. Arrays can do this, but they have to have a strict depth defined. DataLists can be nested arbitrarily deep however you want.

When should I use an array instead of a DataList?

  • When performance is critical, such as iterating over a container every frame. DataLists may have a very small amount of performance overhead due to pulling the value out of the token.
  • When you want to sync the container over the network. DataLists technically support this through JSON if there is a reason you absolutely need a DataList, but that is going to be much more expensive on both performance and bandwidth than normal array syncing.
  • When your container only needs to contain one specific type. DataLists can do this of course, but they bypass the strict typing nature of C#. This means that your code editor will be unable to know exactly what type the container contains, and this may allow you to write bugs that would have otherwise been compile errors.
  • When you want to contain a type that is not directly supported by Data Tokens. Data Tokens can contain any type through the use of object references and boxing, but it's not ideal. You need to pull the reference out and cast it to your desired type.