zod icon indicating copy to clipboard operation
zod copied to clipboard

Properties with Symbol keys are stripped from objects, even with passthrough()

Open dmeehan1968 opened this issue 2 years ago • 3 comments

TL;DR Properties with symbol keys are stripped from objects regardless of the object shape.

ZodRawShape is defined as [k: string]: ZodTypeAny but according to Typescript rules, this does not mean that a passed object should only have string keys. I think this is why Zod allows the object shape but then doesn't handle the presence of Symbol keys.

Therefore there should probably, at least, be a runtime exception to indicate that they are not handled.

const bar = Symbol('bar')

const Schema = z.object({
  foo: z.string(),
  [bar]: z.string()
})

const input: z.infer<typeof Schema> = {
  foo: 'foo',
  [bar]: 'bar',
}

const output = Schema.parse(input)

Results in:

{
  foo: 'foo'
}

It seems that symbol keys are ignored in the object specification, as z.string().default('bar') also does not result in there being a property for Symbol(bar) with value bar in the output.

This also happens if the symbol property is included or omitted, and the schema has .passthrough() added.

dmeehan1968 avatar Sep 13 '23 16:09 dmeehan1968

Hi, @dmeehan1968. I'm Dosu, and I'm helping the Zod team manage their backlog. I'm marking this issue as stale.

Issue Summary:

  • You reported that properties with Symbol keys are removed from objects, even with .passthrough().
  • Suspected cause is ZodRawShape not limiting keys to strings.
  • You suggested a runtime exception for unsupported Symbol keys.
  • The example you provided shows Symbol keys are ignored in parsed results.
  • No further activity or comments have been made on this issue.

Next Steps:

  • Please confirm if this issue is still relevant with the latest version of Zod by commenting here.
  • If there is no response, the issue will be automatically closed in 7 days.

Thank you for your understanding and contribution!

dosubot[bot] avatar Jun 12 '25 16:06 dosubot[bot]

Hello, I can confirm, that this issue is still relevant - even with Zod v4, and loose(), Symbols are not preserved. The only way to preserve symbols is to use z.custom(), but that has its issues (one being it's not a native parser, so you lose the advantages of using z.object())

syyyr avatar Jun 18 '25 08:06 syyyr

Hi @colinhacks, the user @syyyr has confirmed that the issue regarding Symbol keys not being preserved is still relevant even with Zod v4. Could you please assist them with this?

dosubot[bot] avatar Jun 18 '25 08:06 dosubot[bot]

Our usecase for this is that we have this custom format called "CPON", which is essentially a superset of JSON. In this format, there are objects (we call those "Map"), same as in JSON, but then there are "IMap" values, which are objects with numerical keys, to save some bytes by getting rid of the quotes. When parsing these two into JS values, we have to tag them with a Symbol to preserve the type information (as JS only has string keys). We then want to use Zod to confirm a specific shape of the object. However, there are two problems:

  • We can't simply use z.object(), because it doesn't support checking the symbols.
  • We can't use z.object().and(z.custom(...)) to check the symbol by ourselves, because by the time parsing gets to the and, the object has its symbols stripped by z.object().

The only solution that I found is to use z.custom() for the whole thing, but that has a few disadvantages:

  • We can't use things like .strict(), on the resulting parser, because it is not of type ZodObject (unless we implement that for own parser).
  • We also can't use our custom parser in unions (although you can work around that by using and() on the union).
  • We have to check that the symbol is present by ourselves (which is not a big deal I guess).
  • Error messages (issues) are a bit cumbersome to work with in the custom parser, although in v4 (with .check()), things have gotten a lot better.

Another thing I've found out is that z.record() actually does work with Symbol keys, as that one uses some introspection library. If z.object() used the same thing for iteration, then that would work out well, I think. But I guess the introspection library might not be as fast.

syyyr avatar Jul 02 '25 10:07 syyyr

Hi, @dmeehan1968. I'm Dosu, and I'm helping the Zod team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that Zod strips Symbol-keyed properties from objects during parsing, even when using .passthrough(), because z.object() only handles string keys.
  • Another user confirmed this issue persists in Zod v4 and shared a use case involving a custom format requiring Symbol keys for type tagging.
  • It was noted that while z.record() supports Symbol keys, z.object() does not, and z.custom() can be a workaround but has limitations.
  • The main maintainer, @colinhacks, has been asked for input on this issue but no resolution has been posted yet.

Next Steps:

  • Please confirm if this issue is still relevant with the latest version of Zod; if so, you can keep the discussion open by commenting here.
  • Otherwise, I will automatically close this issue in 7 days.

Thanks for your understanding and contribution!

dosubot[bot] avatar Oct 15 '25 16:10 dosubot[bot]

I tested with 4.1.12:

const some_parser = z.object({x: z.string()}).loose();
const MySymbol = Symbol('');
const my_object = {
	[MySymbol]: 'some_string',
	x: 'some_string'
};
const parsed_object = some_parser.parse(my_object);
console.log('parsed_object', '=', parsed_object);
console.log('parsed_object', '=', parsed_object[MySymbol]);

And symbols are still stripped from objects.

syyyr avatar Oct 16 '25 08:10 syyyr