Godot Data Validation with Zodot
Tags: godot, godotengine, gamedev, zod
Published: 2023-10-17 15:07:23

[Zodot](https://github.com/rametta/zodot) is a new data validation library for [Godot](https://godotengine.org/), with an API that was inspired by the popular Typescript/Javascript npm package [Zod](https://www.npmjs.com/package/zod).

Data validation is an important concept in many programming environments, including Godot. It is the process of taking `unknown` data and confirming that it matches the shape that is expected. If the data does not match the
expected shape, then gracefully handle the error.

## Features

Some of Zodot's convenient features include:

- Typed dictionaries in GDScript
- Typed Unions
- Support for nullables
- Automatic data coercing with `str_to_var()`

More details on these features below.

## When to use Zodot

Oftentimes in game development, or any development - developers will store or retrieve data externally from their codebase, either from a database, an API rest endpoint, a file, or just an external package. Such data can be things like a player's inventory system, their statistics, lobby information, friends lists, or more.

No matter how you retrieve some external data, the important step is to validate that the data you received matches the shape that you are expecting, otherwise you may start accessing the data when assuming its shape, but results in a runtime crash when the data has changed unexpectedly.

## How to use Zodot

Using Zodot is super easy. The first thing to do is define a schema, and then use the schema to parse the data that you want to validate.

Example of defining a player schema with 3 fields:

```gdscript
var PlayerSchema = Z.schema({
  "user_id": Z.string(),
  "avatar_color": Z.color(),
  "position": Z.vector3()
})
```

This schema defined an object with 3 typed fields. Now we can take some data and pass it to the schema to be validated against:

```gdscript
func parse_player(data: Variant):
  var result = PlayerSchema.parse(data)

  if not result.ok():
    print("The data does not match the expected shape")
    print(result.error)
    return

  # Do things with the valid data now
  print(result.data)
```

In the above example, we see the data comes in as a `Variant` type, which just means it could be anything, then we pass that to our schema. If our result from the parse is `not ok`, then we have access to a detailed error message as `result.error`. If it does match the schema, then we have access to the typed data in `result.data`.

## Data Coercing

One of the best features of Zodot is its ability to `corece` data before validating. This is popular when complex data is stored as strings and then needs to be deserialized back into its real type after retrieval. Let's look at an example:

```gdscript
var schema = Z.schema({
  "color": Z.color().coerce(),
  "position": Z.vector2().coerce()
})
```

We use the `.coerce()` extension for each field we want to deserialize because we are expecting to receive that field as a string that was previously serialized using Godot's [var_to_str](https://docs.godotengine.org/en/stable/classes/class_@globalscope.html#class-globalscope-method-var-to-str) method.

Now if we have serialized data such as:

```gdscript
# notice the values are wrapped in strings
var data = {
  "color": "Color(1,2,3,.5)",
  "position": "Vector3(4,2,5)"
}
```

We can parse it and receive the actual types after successful validation:

```gdscript
var result = schema.parse(data)
print(result.data.color) # Color(1,2,3,.5) - real type, not wrapped in strings
```

## Typed Dictionaries

Zodot provides some powerful features that are not even available in GDScript, such as typed dictionaries. Take a look at a dictionary example:

```gdscript
var schema = Z.schema({
  "other_field": Z.float(),
  "my_dict": Z.dictionary(Z.color())
})
```

Since GDScript can't define the type for the dictionary values, we can use Zodot's dictionary validator to ensure that all the values in the dictionary are of a certain type - like this example, all values in the dictionary must be a `Color` type.

## Typed Unions

Another powerful validator from Zodot is called `z.union()` which is another feature that GDScript does not provide. This validator allows fields to be more than one type.

```gdscript
Z.schema({
  "user_id": Z.union([Z.string(), Z.integer()])
})
```

In this example, the `user_id` is defined to accept a string or even an integer as a valid type! This is similar to other languages like Typescript which would be defined as: `type UserId = string | number`

## Nullables

One last feature of Zodot is its ability to allow any field to be `null` just by adding `.nullable()` to the end of the field validator.

```gdscript
var nullable_color_field = z.color().nullable()
```

This makes it much more clear than the basic GDScript type definitions since GDScript has no real way of defining that relationship.

## Try it out

Zodot is available on the Godot Asset Store [here](https://godotengine.org/asset-library/asset/2261), and it can also just be downloaded directly from Github [here](https://github.com/rametta/zodot).

Follow me on [Twitter](https://twitter.com/Rametta) to hear more about interesting Godot projects!