Videos
export type HoppEnvPair = { key: string; value: string };
export type HoppEnvs = {
global: HoppEnvPair[];
selected: HoppEnvPair[];
};I have types defined in an upstream package. Is there a library to parse json to a type safe object like `parse(json, TopLevelType)` that can point out validation errors too? I looked at TypedJson, Class Transformer, Zod (nice tiny package) etc but they require me to rewrite those upstream package types as classes or schemas.
I have half a mind to write a schema that parses json to a schema specific object, and then manually convert to upstream types. This seems verbose but it could be typesafe and more straightforward than parsing manually.
TypeScript is (a superset of) JavaScript, so you just use JSON.parse as you would in JavaScript:
let obj = JSON.parse(jsonString);
Only that in TypeScript you can also have a type for the resulting object:
interface MyObj {
myString: string;
myNumber: number;
}
let obj: MyObj = JSON.parse('{ "myString": "string", "myNumber": 4 }');
console.log(obj.myString);
console.log(obj.myNumber);
(code in playground)
Type-safe JSON.parse
You can continue to use JSON.parse, as TypeScript is a superset of JavaScript:
This means you can take any working JavaScript code and put it in a TypeScript file without worrying about exactly how it is written.
There is a problem left: JSON.parse returns any, which undermines type safety (don't use any).
Here are three solutions for stronger types, ordered by ascending complexity:
1. User-defined type guards
Playground
// For example, you expect to parse a given value with `MyType` shape
type MyType = { name: string; description: string; }
// Validate this value with a custom type guard (extend to your needs)
function isMyType(o: any): o is MyType {
return "name" in o && "description" in o
}
const json = '{ "name": "Foo", "description": "Bar" }';
const parsed = JSON.parse(json);
if (isMyType(parsed)) {
// do something with now correctly typed object
parsed.description
} else {
// error handling; invalid JSON format
}
isMyType is called a type guard. Its advantage is, that you get a fully typed object inside truthy if branch.
2. Generic JSON.parse wrapper
Playground
Create a generic wrapper around JSON.parse, which takes one type guard as input and returns the parsed, typed value or error result:
const safeJsonParse = <T>(guard: (o: any) => o is T) =>
(text: string): ParseResult<T> => {
const parsed = JSON.parse(text)
return guard(parsed) ? { parsed, hasError: false } : { hasError: true }
}
type ParseResult<T> =
| { parsed: T; hasError: false; error?: undefined }
| { parsed?: undefined; hasError: true; error?: unknown }
Usage example:
const json = '{ "name": "Foo", "description": "Bar" }';
const result = safeJsonParse(isMyType)(json) // result: ParseResult<MyType>
if (result.hasError) {
console.log("error :/") // further error handling here
} else {
console.log(result.parsed.description) // result.parsed now has type `MyType`
}
safeJsonParse might be extended to fail fast or try/catch JSON.parse errors.
3. External libraries
Writing type guard functions manually becomes cumbersome, if you need to validate many different values. There are libraries to assist with this task - examples (no comprehensive list):
io-ts: hasfp-tspeer dependency, uses functional programming stylezod: strives to be more procedural / object-oriented thanio-tstypescript-is: TS transformer for compiler API, additional wrapper like ttypescript neededtypescript-json-schema/ajv: Create JSON schema from types and validate it withajv
More infos
- Runtime type checking #1573
- Interface type check with Typescript
- TypeScript: validating external data