Search code examples
zod

Is Zod able to parse content of JSON files?


This might be a duplicate of Zod: Parse external JSON file


I have a function that expects a JSON string and parses it to a given type, inferred by a Zod schema

const createConfigurationFromJson = (content: string): Configuration => {
  const rawConfiguration = JSON.parse(content);

  return configurationSchema.parse(rawConfiguration);
};

This function might throw if the JSON content is invalid JSON or Zod throws a parse error. Without using another third party library, is it possible to let Zod parse the JSON string? So I can be sure there can only be a Zod error?


Solution

  • If you want it completely in zod, before you can send that to the configurationSchema, you need to transform the string content to an object, and add an issue if and only if parsing the JSON fails. Then you can use .pipe to pass that to configurationSchema.

    const configurationSchema = z.object({
      name: z.string(),
      version: z.string(),
      description: z.string(),
    });
    
    type Configuration = z.infer<typeof configurationSchema>;
    
    const createConfigurationFromJson = (content: string): Configuration => {
      return z
        .string()
        .transform((_, ctx) => {
          try {
            return JSON.parse(content);
          } catch (error) {
            ctx.addIssue({
              code: z.ZodIssueCode.custom,
              message: 'invalid json',
            });
            return z.never;
          }
        })
        .pipe(configurationSchema)
        .parse(content);
    };
    

    (you could optimize this to use the stored value from the first JSON.parse, but that's just a premature optimization that isn't going to affect the vast majority of projects)

    When tested with

    const configuration1 = createConfigurationFromJson(`{
      "name": "my-app",
      "version": "1.0.0",
      "description": "My awesome app"
    }`);
    
    const configuration2 = createConfigurationFromJson(`{
      "banana": "🍌"
    }`);
    
    const configuration3 = createConfigurationFromJson(`{
      fiadsjfoiajsdoivjdaoij
    `);
    

    it outputs the success and errors as

    configuration1 {
      name: "my-app",
      version: "1.0.0",
      description: "My awesome app"
    }
    configuration2 159 |     const json = JSON.stringify(obj, null, 2);
    160 |     return json.replace(/"([^"]+)":/g, "$1:");
    161 | };
    162 | class ZodError extends Error {
    163 |     constructor(issues) {
    164 |         super();
                ^
    ZodError: [
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "undefined",
        "path": [
          "name"
        ],
        "message": "Required"
      },
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "undefined",
        "path": [
          "version"
        ],
        "message": "Required"
      },
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "undefined",
        "path": [
          "description"
        ],
        "message": "Required"
      }
    ]
     errors: [
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "undefined",
        "path": [
          "name"
        ],
        "message": "Required"
      },
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "undefined",
        "path": [
          "version"
        ],
        "message": "Required"
      },
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "undefined",
        "path": [
          "description"
        ],
        "message": "Required"
      }
    ]
    
          at new ZodError (/Users/sgunter/code/zod-parse-json/node_modules/zod/lib/index.mjs:164:8)
          at /Users/sgunter/code/zod-parse-json/node_modules/zod/lib/index.mjs:537:30
          at parse (/Users/sgunter/code/zod-parse-json/node_modules/zod/lib/index.mjs:636:14)
          at /Users/sgunter/code/zod-parse-json/index.ts:46:25
    
    configuration3 159 |     const json = JSON.stringify(obj, null, 2);
    160 |     return json.replace(/"([^"]+)":/g, "$1:");
    161 | };
    162 | class ZodError extends Error {
    163 |     constructor(issues) {
    164 |         super();
                ^
    ZodError: [
      {
        "code": "custom",
        "message": "invalid json",
        "fatal": true,
        "path": []
      }
    ]
     errors: [
      {
        "code": "custom",
        "message": "invalid json",
        "fatal": true,
        "path": []
      }
    ]
    
          at new ZodError (/Users/sgunter/code/zod-parse-json/node_modules/zod/lib/index.mjs:164:8)
          at /Users/sgunter/code/zod-parse-json/node_modules/zod/lib/index.mjs:537:30
          at parse (/Users/sgunter/code/zod-parse-json/node_modules/zod/lib/index.mjs:636:14)
          at /Users/sgunter/code/zod-parse-json/index.ts:55:25