Hacker News new | ask | show | jobs
by pskinner 2036 days ago
JSON schema to provide documentation across system and runtime validation of both types and business rules.

Much more effective.

3 comments

Using something like io-ts gives you the benefits of static types and runtime types. JSON Schema typically just provides runtime guarantees but doesn't come with the same development experience. I've been very happy with io-ts so far.
I know there’s a zillion libraries in this space by now (and so far I’ve found io-ts quite good), but I feel like there’s actually room for more, with some features currently missing from all of them. (So I’m working on one!)

The dynamic JS space uses things like JSON Schema because they provide documentation and enable runtime validation, in a similar way to libs like io-ts. But they also provide API/network boundary documentation, as well as enable automatically generating client libraries (regardless of the client’s stack).

There’s a good opportunity for a library with all of these value propositions:

- simple, declarative, composable definition interface

- runtime validation/decoding/encoding

- DRY static type generation

- DRY standards-based documentation generation

There are (to my knowledge) no tools available providing all of those in one package. But I’ve built one (it was built on top of io-ts), so I know it can be done. But it was proprietary, for an employer, so I can’t share it.

But! I learned a lot in the process, and I’m building a new one. Zero dependencies, fundamentally a different design/approach. But I’ll definitely be sharing it with HN when it’s ready.

Edit to add: another thing is that many of the tools that provide some subset of those goals (especially static types <-> JSON Schema tools) require is out of band code/file generation (eg supply JSON Schema file input, get .d.ts file output, or vice versa). IMO this is additional DX friction and more error prone than the runtime/compile time parity of the common TS libraries. So another goal for my project is that the default approach is to define one interface and have APIs available to access all the other representations in the same flow, with file generation a secondary concern if there’s a need/desire.

I'm using zod to do the same. If you aren't using fp-ts it will fit a lot easier into your ecosystem.
I use runtypes:

     import { Record, Number, Static } from 'runtypes';

     export const Thing = Record({
       thing: Number
     });

     export type Thing = Static<Thing>;
Then you can import it like this:

     import { Thing } from './thing';

     function (thing: Thing) {
       if (Thing.guard(thing)) { console.log('we have a thing!'); }
     }
You don't really need to be bought into fp-ts to use io-ts. Just create a helper function that morphs the native decode output, `Either<E, A>`, to something like `E | A` or `A | null`.
Or even `asserts value is A` if error handling is more your cup of tea/more idiomatic in your project.
We generate JSON Schema from our TypeScript interfaces, then use that to validate data at relevant points in the application. Best of both worlds, assuming the type definitions are relatively straightforward.
What do you use for that? I looked into it, and none of the alternatives sat well with me. They all felt like impositions on the build process that would make debugging harder.
Thank you
We've had pretty good success with using OpenAPI Specifications to document APIs and their schemas and then using generated clients in code.