Skip to content

Instantly share code, notes, and snippets.

@fsubal
Last active November 16, 2024 06:19
Show Gist options
  • Save fsubal/bc16412ffd169b2406065da0064f712c to your computer and use it in GitHub Desktop.
Save fsubal/bc16412ffd169b2406065da0064f712c to your computer and use it in GitHub Desktop.
import Ajv, { JSONSchemaType, ErrorObject } from 'ajv'
import { FromSchema } from 'json-schema-to-ts'
interface AggregatedValidationError extends AggregateError {
errors: ErrorObject[]
}
/**
* ajvは全スキーマをキャッシュするので、インスタンスごとにnewせず、使い回す
*/
const ajv = new Ajv({ strict: true })
type SafeParseResult<T> =
| { success: true, data: T, error: null }
| { success: false, data: null, error: AggregatedValidationError }
export class JsonSchema<T> {
private readonly validate
/**
* JSON Schemaを使ってパースしてくれるクラス。
*
* NOTICE: ajvの`JSONSchemaType`は、
* optionalなpropertyは必ずnullableだし、
* nullableなものは必ずoptionalでもあるという仮定がある
*
* @see https://github.com/ajv-validator/ajv/issues/1375
*/
constructor(readonly schema: JSONSchemaType<T>, ajvInstance: Ajv = ajv) {
this.validate = ajvInstance.compile(schema)
}
private get lastError(): AggregatedValidationError {
return new AggregateError(this.validate.errors ?? [], `JSON did not match the schema: ${this.schema.$id ?? 'Unknown'}`)
}
/**
* @throws {AggregatedValidationError}
*/
parse(data: unknown): T {
const valid = this.validate(data)
if (valid) {
return data
} else {
throw this.lastError
}
}
safeParse(data: unknown): SafeParseResult<T> {
const valid = this.validate(data)
if (valid) {
return { success: true, data, error: null }
} else {
return { success: false, data: null, error: this.lastError }
}
}
}
const SCHEMA = {
$id: 'User',
type: 'object',
properties: {
id: { type: 'number', nullable: true },
name: { type: 'string' }
},
required: ['name'],
additionalProperties: false
} as const
type User = FromSchema<typeof SCHEMA>
const User = new JsonSchema<User>(SCHEMA)
const user = User.parse({ id: 1, foo: 'John' })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment