avatarBrian Ridolce

Summarize

Working with Complex Data Structures in Zod

Photo by James Harrison on Unsplash

Working with complex data structures can be challenging, especially when it comes to data validation. Fortunately, the Zod library provides a powerful solution for working with complex data structures in TypeScript. In this article, we’ll explore the different features of Zod that make it a great choice for validating complex data structures.

Introduction to Zod

Zod is a TypeScript-first library for data validation. It is designed to be easy to use and provides a lot of features that make it a powerful tool for working with complex data structures. Zod is built on top of the TypeScript type system, which means that it can generate TypeScript types for your validated data. This can make your code easier to read and understand.

Zod provides a wide range of validation capabilities, including built-in validation types for strings, numbers, booleans, and more. It also provides custom validation types, which can be used to define your own validation rules. With Zod, you can easily validate data structures of any complexity, from simple objects to deeply nested data structures.

In the following sections, we’ll explore some of the features of Zod that make it a great choice for working with complex data structures.

Defining Complex Data Structures

To define complex data structures in Zod, we can use the ZodObject type. This type allows us to define an object with multiple properties, each with its own validation rules. For example, let's say we have an object with the following properties:

{
  name: "John Doe",
  age: 30,
  address: {
    street: "123 Main St.",
    city: "Anytown",
    state: "CA",
    zip: "12345"
  }
}

To validate this object, we can define a Zod object like this:

import * as z from "zod";

const personSchema = z.object({
  name: z.string(),
  age: z.number(),
  address: z.object({
    street: z.string(),
    city: z.string(),
    state: z.string(),
    zip: z.string()
  })
});

In this example, we define an object called personSchema that has three properties: name, age, and address. The name property is validated using the string() method, which ensures that the value is a string. The age property is validated using the number() method, which ensures that the value is a number. The address property is itself an object, which is defined using the object() method. The object has four properties: street, city, state, and zip. Each of these properties is validated using the string() method.

With our Zod object defined, we can now use it to validate our data:

const data = {
  name: "John Doe",
  age: 30,
  address: {
    street: "123 Main St.",
    city: "Anytown",
    state: "CA",
    zip: "12345"
  }
};

const result = personSchema.safeParse(data);

if (result.success) {
  // The data is valid
} else {
  // The data is invalid
  console.error(result.error);
}

In this example, we define an object called data that matches our schema. We then use the safeParse() method to validate the data against our schema. If the validation is successful, the success property of the result object will be true. If the validation fails, the success property will be false, and the error property will contain information about the validation error.

Handling Optional Properties

Sometimes, we want to define an object with optional properties. In Zod, we can do this using the optional() method. For example, let's say we have an object with an optional phone property:

{
  name: "John Doe",
  age: 30,
  phone?: "123-456-7890"
}

To define this object in Zod, we can use the optional() method like this:

const personSchema = z.object({
  name: z.string(),
  age: z.number(),
  phone: z.string().optional()
});

In this example, we define an object called personSchema with three properties: name, age, and phone. The phone property is defined using the string() method, just like the name property. However, we also use the optional() method to indicate that the phone property is optional.

With our Zod object defined, we can now use it to validate our data:

const data = {
  name: "John Doe",
  age: 30,
  phone: "123-456-7890"
};

const result = personSchema.safeParse(data);

if (result.success) {
  // The data is valid
} else {
  // The data is invalid
  console.error(result.error);
}

In this example, we define an object called data that matches our schema. We then use the safeParse() method to validate the data against our schema. If the validation is successful, the success property of the result object will be true. If the validation fails, the success property will be false, and the error property will contain information about the validation error.

Working with Arrays

Zod provides built-in validation for arrays using the array() method. This method takes a single argument, which is the validation type for the array elements. For example, let's say we have an array of numbers:

[1, 2, 3, 4, 5]

To validate this array, we can define a Zod schema like this:

const numberArraySchema = z.array(z.number());

In this example, we define an array schema called numberArraySchema that validates that all elements in the array are numbers.

With our Zod schema defined, we can use it to validate our data:

const data = [1, 2, 3, 4, 5];

const result = numberArraySchema.safeParse(data);

if (result.success) {
  // The data is valid
} else {
  // The data is invalid
  console.error(result.error);
}

with optional properties. In Zod, we can do this using the optional() method. For example, let's say we have an object with an optional phone property:

{
  name: "John Doe",
  age: 30,
  phone?: "123-456-7890"
}

To define this object in Zod, we can use the optional() method like this:

const personSchema = z.object({
  name: z.string(),
  age: z.number(),
  phone: z.string().optional()
});

In this example, we define an object called personSchema with three properties: name, age, and phone. The phone property is defined using the string() method, just like the name property. However, we also use the optional() method to indicate that the phone property is optional.

With our Zod object defined, we can now use it to validate our data:

const data = {
  name: "John Doe",
  age: 30,
  phone: "123-456-7890"
};
const result = personSchema.safeParse(data);

if (result.success) {
  // The data is valid
} else {
  // The data is invalid
  console.error(result.error);
}

In this example, we define an object called data that matches our schema. We then use the safeParse() method to validate the data against our schema. If the validation is successful, the success property of the result object will be true. If the validation fails, the success property will be false, and the error property will contain information about the validation error.

Working with Arrays

Zod provides built-in validation for arrays using the array() method. This method takes a single argument, which is the validation type for the array elements. For example, let's say we have an array of numbers:

[1, 2, 3, 4, 5]

To validate this array, we can define a Zod schema like this:

const numberArraySchema = z.array(z.number());

In this example, we define an array schema called numberArraySchema that validates that all elements in the array are numbers.

With our Zod schema defined, we can use it to validate our data:

const data = [1, 2, 3, 4, 5];
const result = numberArraySchema.safeParse(data);
if (result.success) {
  // The data is valid
} else {
  // The data is invalid
  console.error(result.error);
}

In this example, we define an array called data that matches our schema. We then use the safeParse() method to validate the data against our schema. If the validation is successful, the success property of the result object will be true. If the validation fails, the success property will be false, and the error property will contain information about the validation error.

Working with Union Types

In some cases, we may want to define a schema that can accept multiple types of data. For example, let’s say we have a property that can be either a string or a number:

{
  name: "John Doe",
  age: 30,
  phone: "123-456-7890",
  alternatePhone: 5555555555
}

To define this property in Zod, we can use the union() method like this:

const personSchema = z.object({name: z.string(),
age: z.number(),
phone: z.string().optional(),
alternatePhone: z.union([z.string(), z.number()])
});

In this example, we define an object called `personSchema` with four properties: `name`, `age`, `phone`, and `alternatePhone`. The `alternatePhone` property is defined using the `union()` method, which accepts an array of validation types. In this case, we specify that the `alternatePhone` property can be either a `string` or a `number`.

With our Zod object defined, we can now use it to validate our data:

const data = {
  name: "John Doe",
  age: 30,
  phone: "123-456-7890",
  alternatePhone: 5555555555
};

const result = personSchema.safeParse(data);

if (result.success) {
  // The data is valid
} else {
  // The data is invalid
  console.error(result.error);
}

In this example, we define an object called data that matches our schema. We then use the safeParse() method to validate the data against our schema. If the validation is successful, the success property of the result object will be true. If the validation fails, the success property will be false, and the error property will contain information about the validation error.

Working with Complex Data Structures

Sometimes we may have to work with more complex data structures, such as nested objects and arrays. In these cases, Zod can be a powerful tool for ensuring that our data is valid.

Let’s say we have an array of objects, where each object represents a person:

[
  {
    name: "John Doe",
    age: 30,
    phone: "123-456-7890",
    address: {
      street: "123 Main St",
      city: "Anytown",
      state: "CA",
      zip: "12345"
    }
  },
  {
    name: "Jane Smith",
    age: 40,
    phone: "555-555-5555",
    address: {
      street: "456 Oak St",
      city: "Somewhere",
      state: "CA",
      zip: "67890"
    }
  }
]

To define a Zod schema for this data, we can use the array() and object() methods, along with nested schemas. For example:

const addressSchema = z.object({
  street: z.string(),
  city: z.string(),
  state: z.string(),
  zip: z.string()
});

const personSchema = z.object({
  name: z.string(),
  age: z.number(),
  phone: z.string().optional(),
  address: addressSchema
});

const peopleArraySchema = z.array(personSchema);

In this example, we define a schema called addressSchema for the address property, and a schema called personSchema for each object in the array. We then use the array() method to define a schema for the entire array.

With our Zod schema defined, we can use it to validate our data:

const data = [
  {
    name: "John Doe",
    age: 30,
    phone: "123-456-7890",
    address: {
      street: "123 Main St",
      city: "Anytown",
      state: "CA",
      zip: "12345"
    }
  },
  {
    name: "Jane Smith",
    age: 40,
    phone: "555-555-5555",
    address: {
      street: "456 Oak St",
      city: "Somewhere",
      state: "CA",
      zip: "67890"
      }
    }
];

const result = peopleArraySchema.safeParse(data);

if (result.success) {
// The data is valid
} else {
// The data is invalid
console.error(result.error);
}

In this example, we define an array of objects called `data` that matches our schema. We then use the `safeParse()` method to validate the data against our schema. If the validation is successful, the `success` property of the result object will be `true`. If the validation fails, the `success` property will be `false`, and the `error` property will contain information about the validation error.

Conclusion

In this article, we’ve explored how to work with complex data structures in Zod. We started by looking at the basics of defining and using Zod schemas, and then we delved into more complex scenarios, such as nested objects and arrays. Using Zod to validate our data can help ensure that our applications are robust and error-free. With its simple and intuitive API, Zod can be a valuable tool for any JavaScript developer who needs to work with data.

If you loved what you read, would you be able to buy me a cup of coffee? It’s okay if you can’t right now.

Stackademic

Thank you for reading until the end. Before you go:

  • Please consider clapping and following the writer! 👏
  • Follow us on Twitter(X), LinkedIn, and YouTube.
  • Visit Stackademic.com to find out more about how we are democratizing free programming education around the world.
JavaScript
Nextjs
Web Development
Development
Recommended from ReadMedium