Ask
341
@goldenhourgus ·

is casting with `as` after a zod parse normal or am i doing types wrong Inference

Six months into TypeScript, coming from JS. I parse a request body with zod and then write const input = schema.parse(body) as CreateUserInput because the hover tooltip on the parse result is an unreadable wall of ZodObject<{...}> and I already have a CreateUserInput interface defined for the rest of the app.

It compiles, it works, but it feels like I am doing something the language is trying to stop me from doing. Is this a normal thing people do or is it a beginner smell?

10 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @orm_tamsin · 8mo ago · 3 replies

    It is a smell, and the specific thing it means is that you have two sources of truth for the same shape.

    parse() already returns exactly the right type. Derive the interface from the schema instead of declaring it separately and casting to it:

    const CreateUser = z.object({
      email: z.string().email(),
      age: z.number().int(),
    })
    type CreateUser = z.infer<typeof CreateUser>
    

    Now there is one definition. Change the schema and every consumer updates. With the cast, the day someone adds a required field to the schema and not the interface, nothing tells you — the cast is you instructing the compiler to stop checking that exact relationship.

    On the unreadable tooltip: that is a display problem, not a type problem. The ZodObject<...> wall is the schema's type; z.infer of it is a plain object type and hovers fine.

    188
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @headless_hugo · 8mo ago

      The hover tooltip thing catches everyone. If you want readable hovers on any derived type:

      type Pretty<T> = { [K in keyof T]: T[K] } & {}
      

      Wrap and the tooltip flattens. Changes nothing at runtime and nothing about assignability, it just makes the editor tell you the truth in a readable way.

      34
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @goldenhourgus · 8mo ago

      One definition instead of two. I have been maintaining both by hand for six months and had genuinely not made the connection that the cast was the thing letting them drift.

      52
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @two_hills_up · 8mo ago

    The other legitimate use is a library whose own types are wrong, where as unknown as X is the only way through. When you do it, leave a comment with a link to the upstream issue. Untracked casts become permanent, and in two years nobody will remember whether the library was fixed.

    44
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @wellhead_wanda · 8mo ago

    General rule that will serve you for years: every as is a small IOU written against future you.

    Some are fine. as const is not really a cast. Narrowing an unknown you just validated is fine. as Foo on data you have not validated is how you get a runtime error that reads like the type system lied to you.

    Yours is the middle case — harmless today because the schema and the interface happen to match, wrong the moment one of them changes. Which is why the advice above is to delete one of them rather than to write the cast more carefully.

    One exception worth knowing: if you genuinely must keep a hand-written interface, because it is public API documentation or someone else imports it, do not cast between them. Assert they agree in both directions with two throwaway assignments in a test file, so drift becomes a compile error rather than a production surprise.

    96
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @resole_ruth · 8mo ago · 2 replies

    Cheap health check you can run right now:

    grep -rc " as " src | awk -F: '{s+=$2} END {print s}'
    

    Under about twenty in a real codebase and you are doing fine. Four hundred and you have a JavaScript codebase wearing a TypeScript hat, and the fix is not more casts, it is finding the two or three places generating them.

    61
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @goldenhourgus · 8mo ago

      31, and 19 of them are in the same two files, both of which parse things. So the pattern is exactly the one being described here rather than scattered everywhere.

      22
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @overlap_owen · 8mo ago

    Doing types slightly wrong for six months is how literally everyone learns this language. The fact that the cast felt bad enough to ask about means your instincts are already ahead of your knowledge, which is the right way round.

    27
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @burr_bennet · 8mo ago · 2 replies

    as is "trust me" with a keyword.

    14
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @hemline_hana · 8mo ago

      And as unknown as is "trust me, I have thought about this", which is true about 40% of the time.

      9
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report