zod icon indicating copy to clipboard operation
zod copied to clipboard

ZodError#message cannot deal with circular data structures in its `issues`

Open jandockx opened this issue 1 year ago • 1 comments

Example

A Mocha test that shows the issue:

/* eslint-env mocha */

const { z, ZodError } = require('zod')

describe('zod', function () {
  it('cannot deal with circular data structures', function () {
    const AnObjectSchema = z.object({ someLiteralProperty: z.literal(1) })

    const cicrularObject = {
      aProperty: 'a property',
      anotherProperty: 137,
      anObjectProperty: { anObjectPropertyProperty: 'an object property property' },
      anArrayProperty: [{ anArrayObjectPropertyProperty: 'an object property property' }]
    }
    cicrularObject.anObjectProperty.cicrularObject = cicrularObject
    cicrularObject.anArrayProperty.push(cicrularObject.anObjectProperty)
    const violatingObject = { someLiteralProperty: cicrularObject }

    const { success, error } = AnObjectSchema.safeParse(violatingObject)

    success.should.be.false()
    error.should.be.an.instanceof(ZodError)
    error.message.should.be.a.String()
  })
})

This test fails with

TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    |     property 'anObjectProperty' -> object with constructor 'Object'
    --- property 'cicrularObject' closes the circle
    at JSON.stringify (<anonymous>)
    at get message [as message] (node_modules/zod/lib/ZodError.js:105:21)
    at Context.<anonymous> (test/00.zodIssue.js:23:11)
    at process.processImmediate (node:internal/timers:478:21)

Analysis

ZodError.ts, line 283:

  get message() {
    return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2);
  }

JSON.stringify is called on this.issues. JSON.stringify cannot cope with circular data structures. In the example, the circular data structure appears in the received property of the issue.

jsonStringifyReplacer does not deal with this either:

helpers/util.ts#jsonStringifyReplacer, line 91:

  export const jsonStringifyReplacer = (_: string, value: any): any => {
    if (typeof value === "bigint") {
      return value.toString();
    }
    return value;
  };

jandockx avatar Apr 19 '24 12:04 jandockx

Good catch. For security reasons, Zod isn't supposed to print the input data when it throws an error...but you've found the exception here with ZodInvalidLiteralIssue. This will be fixed in Zod 4.

colinhacks avatar Apr 23 '24 00:04 colinhacks