question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Generic for parsing objects

See original GitHub issue

Really enjoy the library! Thank you for the work!

To easier debug when a parse fails, I made a small wrapper like this:

function parse(parser, obj) {
  try {
    return parser.parse(obj);
  } catch (e) {
    console.log(obj);
    throw e;
  }
}

The problem is that typescript can not infer this function.

I tried multiple combinations but can not seem to get it right.

I looked through issues already mentioning generic and did fine one small example: https://github.com/vriad/zod/issues/93 but it did not help fully.

function parse<K, T extends z.ZodType<K, any>>(parser: T, obj): K {
  try {
    return parser.parse(obj);
  } catch (e) {
    console.log(obj);
    throw e;
  }
}
function parse<T extends z.ZodType<any, any>, K typeof T>(parser: T, obj): K {
  try {
    return parser.parse(obj);
  } catch (e) {
    console.log(obj);
    throw e;
  }
}
interface Schema<T> {
  parse: (data:unknown): T;
  check: (data:unknown): data is T;
}
function parse<T extends Schema<K>>(parser: T, obj) {
  try {
    return parser.parse(obj);
  } catch (e) {
    console.log(obj);
    throw e;
  }
}
interface SchemaVal {
  parse<K>(data: unknown): K;
}

function parse<T extends SchemaVal>(parser: T, obj): K {
  try {
    return parser.parse(obj);
  } catch (e) {
    console.log(obj);
    throw e;
  }
}

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:3
  • Comments:8 (8 by maintainers)

github_iconTop GitHub Comments

13reactions
colinhackscommented, Aug 3, 2020

Hey @kevinsimper -

Good question. Generics are very fussy. I need to do a better job documenting how to write generic helper functions like this. Here’s how to do what you want:

export const parse = <T extends z.ZodTypeAny>(schema: T, data: unknown): z.infer<T> => {
  try {
    return schema.parse(data);
  } catch(err) {
    // handle error
    throw new Error();
  }
}  

Though I think a “parse factory” pattern would be even better so you don’t need to keep passing the schema as an argument to parse:

export const parseFactory = <T extends z.ZodTypeAny>(schema: T) => (data: unknown): z.infer<T> => {
  try {
    return schema.parse(data);
  } catch (err) {
    // handle error
    throw new Error();
  }
};

Then you can use it like this:

const User = z.object({ name: z.string() });
const parseUser = parseFactory(User);
const myUser = parseUser({ name: 'billy jean' });
2reactions
chrbalacommented, Aug 1, 2020

I have written this which has somewhat different goals as the above example, which may be of interest to people:

  • Intentional (validation) errors returned, not thrown. This distinguishes them from unintentional errors which are still thrown.
  • Semi-strongly typed errors (properties known, but not enumerated).
  • Always async
  • Not concerned with creating factories, can curry if desired
  • Decoupled from Zod. Returns errors that are not library specific.
import { Schema, ZodError, ZodErrorCode } from 'zod';

const asError = (e: unknown) =>
  e instanceof Error ? e : typeof e === 'string' ? new Error(e) : new Error();

export type ExecResult<T> =
  | {
      success: true;
      data: T;
    }
  | {
      success: false;
      error: Error;
    };

export const safeExec = <T extends any>(cb: () => T): ExecResult<T> => {
  try {
    return { success: true, data: cb() };
  } catch (e) {
    return { success: false, error: asError(e) };
  }
};

export const safeExecAsync = <T extends any>(
  cb: () => Promise<T>
): Promise<ExecResult<T>> => {
  // check for sync errors
  const res = safeExec(cb);
  if (!res.success) return Promise.resolve(res);

  return Promise.resolve(res.data).then(
    data => ({ success: true, data }),
    error => ({ success: false, error: asError(error) })
  );
};

type ParseError = {
  code: string;
  path: Array<string>;
};
type ParseResult<T> =
  | { success: true; data: T }
  | { success: false; errors: Array<ParseError> };
export const parse = async <T>(
  schema: Schema<T>,
  data: unknown
): Promise<ParseResult<T>> => {
  const res = await safeExecAsync(() => schema.parseAsync(data));
  if (res.success) return res;

  if (res.error instanceof ZodError) {
    return {
      success: false,
      errors: res.error.errors.map(({ code, message, path }) => ({
        path: path.map(String),
        code: code === ZodErrorCode.custom_error ? message : code,
      })),
    };
  }

  throw res.error;
};

Usage:

const parsed = await parse(MySchema, someValue);

if (!parsed.success) {
  return {
    __typename: 'InputError',
    errors: parsed.errors,
  };
}

// parsed.data is known to exist statically now because of the guard above.
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to parse a json to object with a list of generic? [duplicate]
I'd like to convert an object into json and then parse the json back to the original object. The difficulty is there are...
Read more >
Example Using Generic (JSON) Resource Objects - Bloomreach
Resource is the primary object representation to solve all the complex integration problems in a generic way. So, the interface was designed to...
Read more >
Parsing JSON to Generic Class Object with Gson
Parsing JSON to Generic Class Object with Gson ... We can parse this JSON string into a String array : Gson gson =...
Read more >
MS | Generic JSON Parsing - Mark Struzinski
I'll present a simple generic object and its associated json in this post, and then show how that expanded out to a pretty...
Read more >
Generic JSON parsing using Protocol Oriented Programming
The main usage of Parseable object is based on injecting Parseable concrete instance to an asynchronous (escaping closure) generic method that ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found