stefanterdell / json-schema-to-zod Goto Github PK
View Code? Open in Web Editor NEWLicense: ISC License
License: ISC License
Given this schema:
{
"title": "PlanRequest",
"type": "object",
"properties": {
"employees": {
"title": "Employees",
"type": "array",
"items": {
"$ref": "#/definitions/Employee"
}
},
"shifts": {
"title": "Shifts",
"type": "array",
"items": {
"$ref": "#/definitions/Shift"
}
}
},
"required": [
"employees",
"shifts"
],
"definitions": {
"NumberRange": {
"title": "NumberRange",
"type": "object",
"properties": {
"min": {
"title": "Min",
"type": "number"
},
"max": {
"title": "Max",
"type": "number"
}
}
},
"TimeConstraints": {
"title": "TimeConstraints",
"type": "object",
"properties": {
"day": {
"$ref": "#/definitions/NumberRange"
},
"week": {
"$ref": "#/definitions/NumberRange"
},
"month": {
"$ref": "#/definitions/NumberRange"
}
}
},
"Employee": {
"title": "Employee",
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"baseHourlyWage": {
"title": "Basehourlywage",
"type": "number"
},
"time_constraints": {
"$ref": "#/definitions/TimeConstraints"
},
"roles": {
"title": "Roles",
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"id",
"name",
"baseHourlyWage"
]
},
"Shift": {
"title": "Shift",
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"start": {
"title": "Start",
"type": "string",
"format": "date-time"
},
"end": {
"title": "End",
"type": "string",
"format": "date-time"
},
"required_roles": {
"title": "Required Roles",
"default": [],
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"id",
"start",
"end"
]
}
}
}
The live demo gives me a usable schema with all the nested types parsed properly:
import { z } from "zod";
export default z.object({
employees: z.array(
z.object({
id: z.string(),
name: z.string(),
baseHourlyWage: z.number(),
time_constraints: z
.object({
day: z
.object({ min: z.number().optional(), max: z.number().optional() })
.optional(),
week: z
.object({ min: z.number().optional(), max: z.number().optional() })
.optional(),
month: z
.object({ min: z.number().optional(), max: z.number().optional() })
.optional(),
})
.optional(),
roles: z.array(z.string()).optional(),
})
),
shifts: z.array(
z.object({
id: z.string(),
start: z.string(),
end: z.string(),
required_roles: z.array(z.string()).optional(),
})
),
});
But running the CLI locally returns the following schema:
import { z } from "zod";
export const schema = z.object({
employees: z.array(z.any()),
shifts: z.array(z.any()),
});
From looking through the code, I'm actually confused as to why the online version doesn't return the same schema as I get locally. Given that #1 exists, and the above schema uses $ref
, and there is not a single mention of definitions
anywhere in the code, I can't see how the online demo can work at all.
I've also traced the flow of execution in my local version, and it's essentially parseSchema -> parseSchema on all properties -> parseArray -> parseSchema on each item in array -> parseDefault because of ref
.
I have a JSONSchema4 object that I need to convert to Zod. The readme.md says draft JSONSchema4+ is supported, but when I try to convert my schema, I get the following error:
Argument of type 'JSONSchema4' is not assignable to parameter of type 'JSONSchema7'.
Indeed, as I see in the code only a JSONSchema7
type is allowed. Is there a way to convert a JSONSchema4
instance?
For the below JSON schema. I am getting unexpected zod schema as output.
{ "required": [ "entityIdentification" ], "properties": { "entityIdentification": { "maxLength": 80, "minLength": 1, "type": "string" }, "contentOwner": { "required": [ "gln" ], "properties": { "gln": { "pattern": "\\d{13}", "type": "string" }, "additionalPartyIdentification": { "oneOf": [ { "type": "object", "allOf": [ { "maxLength": 80, "minLength": 1, "type": "string" }, { "required": [ "content", "@additionalPartyIdentificationTypeCode", "@codeListVersion" ], "properties": { "content": { "type": "string" }, "@additionalPartyIdentificationTypeCode": { "enum": [ "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY", "CASHSSP", "CNPJ", "COMPANY_CAD", "DEA_DRUG_ENFORCEMENT_AGENCY", "DUNS", "DUNS_PLUS_FOUR", "EU_VAT_IDENTIFICATION_NUMBER", "FOR_INTERNAL_USE_1", "FOR_INTERNAL_USE_10", "FOR_INTERNAL_USE_11", "FOR_INTERNAL_USE_12", "FOR_INTERNAL_USE_13", "FOR_INTERNAL_USE_14", "FOR_INTERNAL_USE_15", "FOR_INTERNAL_USE_16", "FOR_INTERNAL_USE_17", "FOR_INTERNAL_USE_18", "FOR_INTERNAL_USE_19", "FOR_INTERNAL_USE_2", "FOR_INTERNAL_USE_20", "FOR_INTERNAL_USE_3", "FOR_INTERNAL_USE_4", "FOR_INTERNAL_USE_5", "FOR_INTERNAL_USE_6", "FOR_INTERNAL_USE_7", "FOR_INTERNAL_USE_8", "FOR_INTERNAL_USE_9", "HIN_CANADIAN_HEALTHCARE_IDENTIFICATION_NUMBER", "PARTITA_IVA", "SCAC", "SELLER_ASSIGNED_IDENTIFIER_FOR_A_PARTY", "SIRET", "SRN", "TD_LINK_TRADE_DIMENSIONS", "UCC_COMMUNICATION_IDENTIFICATION", "UN_LOCATION_CODE", "UNKNOWN", "USDA_ESTABLISHMENT_NUMBER" ], "type": "string" }, "@codeListVersion": { "maxLength": 35, "minLength": 1, "type": "string" } } } ] }, { "items": { "type": "object", "allOf": [ { "maxLength": 80, "minLength": 1, "type": "string" }, { "required": [ "content", "@additionalPartyIdentificationTypeCode", "@codeListVersion" ], "properties": { "content": { "type": "string" }, "@additionalPartyIdentificationTypeCode": { "enum": [ "BUYER_ASSIGNED_IDENTIFIER_FOR_A_PARTY", "CASHSSP", "CNPJ", "COMPANY_CAD", "DEA_DRUG_ENFORCEMENT_AGENCY", "DUNS", "DUNS_PLUS_FOUR", "EU_VAT_IDENTIFICATION_NUMBER", "FOR_INTERNAL_USE_1", "FOR_INTERNAL_USE_10", "FOR_INTERNAL_USE_11", "FOR_INTERNAL_USE_12", "FOR_INTERNAL_USE_13", "FOR_INTERNAL_USE_14", "FOR_INTERNAL_USE_15", "FOR_INTERNAL_USE_16", "FOR_INTERNAL_USE_17", "FOR_INTERNAL_USE_18", "FOR_INTERNAL_USE_19", "FOR_INTERNAL_USE_2", "FOR_INTERNAL_USE_20", "FOR_INTERNAL_USE_3", "FOR_INTERNAL_USE_4", "FOR_INTERNAL_USE_5", "FOR_INTERNAL_USE_6", "FOR_INTERNAL_USE_7", "FOR_INTERNAL_USE_8", "FOR_INTERNAL_USE_9", "HIN_CANADIAN_HEALTHCARE_IDENTIFICATION_NUMBER", "PARTITA_IVA", "SCAC", "SELLER_ASSIGNED_IDENTIFIER_FOR_A_PARTY", "SIRET", "SRN", "TD_LINK_TRADE_DIMENSIONS", "UCC_COMMUNICATION_IDENTIFICATION", "UN_LOCATION_CODE", "UNKNOWN", "USDA_ESTABLISHMENT_NUMBER" ], "type": "string" }, "@codeListVersion": { "maxLength": 35, "minLength": 1, "type": "string" } } } ] }, "type": "array" } ] } }, "type": "object" } }, "type": "object" }
`import { z } from "zod";
export default z.object({
entityIdentification: z.string().min(1).max(80),
contentOwner: z
.object({
gln: z.string().regex(new RegExp("\d{13}")),
additionalPartyIdentification: z
.any()
.superRefine((x, ctx) => {
const schemas = [z.record(z.any()), z.array(z.record(z.any()))];
const errors = schemas.reduce(
(errors: z.ZodError[], schema) =>
((result) =>
"error" in result ? [...errors, result.error] : errors)(
schema.safeParse(x)
),
[]
);
if (schemas.length - errors.length !== 1) {
ctx.addIssue({
path: ctx.path,
code: "invalid_union",
unionErrors: errors,
message: "Invalid input: Should pass single schema",
});
}
})
.optional(),
})
.optional(),
});`
Please help me with this.
If you run json-schema-to-zod
with -t
set to a directory which doesn't exist yet, it will fail on writeFileSync
Failed to write result to ./src/__generated__/json/deadNotice.ts
Error: ENOENT: no such file or directory, open './src/__generated__/json/deadNotice.ts'
at Object.openSync (node:fs:585:3)
at writeFileSync (node:fs:2153:35)
at Object.<anonymous> (/home/jakobo/code/taskless/monorepo/node_modules/.pnpm/[email protected]/node_modules/json-schema-to-zod/cli.js:98:36)
This can be worked around by creating the directory in advance using a tool such as shx
, so it's a low priority bug relative to issues related to parsing / typing.
When working with oneOf/anyOf/allOf, I've noticed these are not translated back into zod. I tested these outputs against the online tool
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": [
"projectId"
],
"properties": {
"projectId": {
"type": "string",
"format": "uuid"
}
},
"oneOf": [
{
"type": "object",
"properties": {
"userId": {
"type": "string",
"format": "uuid"
}
},
"required": [
"userId"
]
},
{
"type": "object",
"properties": {
"userIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
}
},
"required": [
"userIds"
]
}
]
}
Expected (kind of subjective, as there's several ways in zod to express this):
import { z } from "zod";
export default z.object({ projectId: z.string().uuid() })
.and(
z.union([
z.object({
userIds: z.array(z.string().uuid()),
}),
z.object({
userId: z.string().uuid(),
}),
])
);
Actual (didn't include the additional properties at all):
import { z } from "zod";
export default z.object({ projectId: z.string().uuid() });
Hello
It seems that on version 0.1.4, the content of the published tarball is invalid:
Which is causing the following error:
> require('json-schema-to-zod');
Uncaught:
Error: Cannot find module '/some/path/node_modules/json-schema-to-zod/index.js'. Please verify that the package.json has a valid "main" entry
Link to the tarball: https://registry.npmjs.org/json-schema-to-zod/-/json-schema-to-zod-0.1.4.tgz
Following schema causes issues
{
"properties": {
"languageISOCode": {
"type": "string",
"description": "The language in which the provider details are provided. For example, a site supports two languages English and French. English being the primary language, the provider response will be provided in French depending on the user's locale. The language follows the two letter ISO code.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"favicon": {
"type": "string",
"description": "Favicon link of the provider.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"accountType": {
"type": "array",
"contains": {
"type": "Enum"
},
"isReadOnly": true
},
"countryISOCode": {
"type": "string",
"description": "Country to which the provider belongs.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"isAddedByUser": {
"type": "string",
"description": "Indicates that the site has been added by the user at least once.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"PRIORITY": {
"type": "Enum",
"isReadOnly": true
},
"associatedProviderIds": {
"type": "array",
"contains": {
"type": "number",
"format": "int64"
},
"isReadOnly": true
},
"primaryLanguageISOCode": {
"type": "string",
"description": "The primary language of the site.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"help": {
"type": "string",
"description": "Text to guide user through linking an account that belongs to the site<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"baseUrl": {
"type": "string",
"description": "The base URL of the provider's site.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"capability": {
"type": "array",
"contains": {
"type": "Capability"
},
"isReadOnly": true
},
"loginForm": {
"type": "array",
"contains": {
"type": "LoginForm"
},
"isReadOnly": true
},
"isConsentRequired": {
"type": "boolean",
"description": "Indicates if a provider site requires consent.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"loginUrl": {
"type": "string",
"description": "The login URL of the provider's site.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"isAutoRefreshEnabled": {
"type": "boolean",
"description": "Indicates if a provider site is auto-refreshed.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"name": {
"type": "string",
"description": "The name of a provider site.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"logo": {
"type": "string",
"description": "The logo link of the provider institution. The link will return the logo in the PNG format.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"id": {
"type": "number",
"description": "Unique identifier for the provider site(e.g., financial institution sites, biller sites, lender sites, etc.).<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true,
"format": "int64"
},
"lastModified": {
"type": "string",
"description": "Determines when the provider information was updated by Yodlee. If the customer caches the data, the cache is recommended to be refreshed based on this field.<br><br><b>Endpoints</b>:<ul><li>GET providers/{providerId}</li><li>GET providers</li></ul>",
"isReadOnly": true
},
"authParameter": {
"type": "array",
"contains": {
"type": "Enum"
},
"isReadOnly": true
},
"authType": {
"type": "Enum",
"isReadOnly": true
},
"dataset": {
"type": "array",
"contains": {
"type": "ProvidersDataset"
},
"isReadOnly": true
},
"status": {
"type": "Enum",
"isReadOnly": true
}
}
}
It generates into zod type any
The existing example in the readme is sufficient to get a general idea, but doesn't really show the full extent of what this is capable from. I think this library could benefit from introducing more examples, and maybe even setting up an examples/
directory if someone wants to go overboard on this.
I'm trying to do the following
const parsed = parseSchema(schema)
parsed.parse(lonenModel)
But this just returns a string
z.object({"id":z.number().int(),"klant_id":z.union([z.string(),z.null()]).optional(),"lgrp_id":z.union([z.string(),z.null()]).optional(),"wgnr_id":z.union([z.string(),z.null()]).optional(),"klant_naam":z.union([z.string(),z.null()]).optional(),"datum_afdruk":z.union([z.string(),z.null()]).optional(),"tijd_afdruk":z.union([z.string(),z.null()]).optional(),"extensie":z.union([z.string(),z.null()]).optional(),"kopies":z.union([z.string(),z.null()]).optional(),"extra-info":z.union([z.string(),z.null()]).optional(),"opdrachtnr":z.union([z.string(),z.null()]).optional(),"EfichesPdfPcl":z.union([z.string(),z.null()]).optional()})
Thank you for the awesome library!
I want an Native enums option.
e.g.
input schema
{
"type": "object",
"properties": {
"testType": {
"type": "number",
"enum": [
1,
2
]
}
}
}
output
import { z } from "zod";
export default z.object({ testType: z.enum([1, 2]).optional() });
Zod enums only support string types, so you will get an error.
expected results
import { z } from "zod";
const testType = {
NUMBER_1: 1,
NUMBER_2: 2,
} as const;
export default z.object({ testType: z.nativeEnum(testType).optional() });
I am trying to batch process the json schema from Google's source:
This only generates the following:
import { z } from "zod";
export default z.any();
{
"$id": "https://googleapis.github.io/google-cloudevents/jsonschema/google/events/cloud/apigateway/v1/GatewayEventData.json",
"name": "GatewayEventData",
"examples": [],
"package": "google.events.cloud.apigateway.v1",
"datatype": "google.events.cloud.apigateway.v1.GatewayEventData",
"cloudeventTypes": [
"google.cloud.apigateway.gateway.v1.created",
"google.cloud.apigateway.gateway.v1.updated",
"google.cloud.apigateway.gateway.v1.deleted"
],
"product": "API Gateway",
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "#/definitions/GatewayEventData",
"definitions": {
"GatewayEventData": {
"properties": {
"payload": {
"$ref": "#/definitions/Gateway",
"additionalProperties": true,
"description": "Optional. The Gateway event payload. Unset for deletion events."
}
},
"additionalProperties": true,
"type": "object",
"title": "Gateway Event Data",
"description": "The data within all Gateway events."
},
"Gateway": {
"properties": {
"name": {
"type": "string",
"description": "Output only. Resource name of the Gateway. Format: projects/{project}/locations/{location}/gateways/{gateway}"
},
"createTime": {
"type": "string",
"description": "Output only. Created time.",
"format": "date-time"
},
"updateTime": {
"type": "string",
"description": "Output only. Updated time.",
"format": "date-time"
},
"labels": {
"additionalProperties": {
"type": "string"
},
"type": "object",
"description": "Optional. Resource labels to represent user-provided metadata. Refer to cloud documentation on labels for more details. https://cloud.google.com/compute/docs/labeling-resources"
},
"displayName": {
"type": "string",
"description": "Optional. Display name."
},
"apiConfig": {
"type": "string",
"description": "Required. Resource name of the API Config for this Gateway. Format: projects/{project}/locations/global/apis/{api}/configs/{apiConfig}"
},
"state": {
"enum": [
"STATE_UNSPECIFIED",
0,
"CREATING",
1,
"ACTIVE",
2,
"FAILED",
3,
"DELETING",
4,
"UPDATING",
5
],
"oneOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"title": "State",
"description": "All the possible Gateway states."
},
"defaultHostname": {
"type": "string",
"description": "Output only. The default API Gateway host name of the form `{gateway_id}-{hash}.{region_code}.gateway.dev`."
}
},
"additionalProperties": true,
"type": "object",
"title": "Gateway",
"description": "A Gateway is an API-aware HTTP proxy. It performs API-Method and/or API-Consumer specific actions based on an API Config such as authentication, policy enforcement, and backend selection."
}
}
}
Great package, but I was wondering how I can use this library at runtime, because the output will be a string instead of the zod object structure.
We have an endpoint that returns the json schema and I would like to convert the schema runtime to zod.
Thanks in advance
const { z } = require('zod');
const {
jsonSchemaToZod,
jsonSchemaToZodDereffed,
parseSchema,
} = require('json-schema-to-zod');
const myObject = {
type: 'object',
properties: {
custom: {
type: 'custom',
},
}
};
const custom = z
.string()
.email()
.refine(
data => {
return data;
},
{
message: "Passwords don't match",
}
)
.optional();
// z.object({"custom":custom})
const schema = parseSchema(myObject);
const fn = new Function('z', 'custom', `return ${schema}`);
const instance = fn(z,custom);
When processing the compose spec schema (available from https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json) all top-level object values will be of type z.any()
. The validator will output rich types if we remove patternProperties
from the schema.
If I'm not mistaken, we should, however, be able to handle patternProperties
by using a z.record(keyType, valueType)
(https://github.com/colinhacks/zod#record-key-type).
Is it intended or it's a bug (or a feature)? Or maybe I am missing something? Let's say we have the following input JSON schema:
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "config",
"description": "~/.bashrc config",
"definitions": {
"color": {
"type": "string",
"enum": [
"none",
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"light-gray",
"gray",
"light-red",
"light-green",
"light-yellow",
"light-blue",
"light-magenta",
"light-cyan",
"white"
]
}
},
"type": "object",
"properties": {
"messages": {
"title": "messages settings",
"description": "Message's settings",
"type": "object",
"properties": {
"info": {
"title": "info settings",
"description": "Info settings",
"type": "object",
"properties": {
"foreground": {
"description": "An info foreground color",
"$ref": "#/definitions/color"
},
"background": {
"description": "An info background color",
"$ref": "#/definitions/color"
}
},
"additionalProperties": false
},
"warn": {
"title": "warning settings",
"description": "Warning settings",
"type": "object",
"properties": {
"foreground": {
"description": "A warning foreground color",
"$ref": "#/definitions/color"
},
"background": {
"description": "A warning background color",
"$ref": "#/definitions/color"
}
},
"additionalProperties": false
},
"error": {
"title": "error settings",
"description": "Error settings",
"type": "object",
"properties": {
"foreground": {
"description": "An error foreground color",
"$ref": "#/definitions/color"
},
"background": {
"description": "An error background color",
"$ref": "#/definitions/color"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"prompt": {
"title": "prompt settings",
"description": "Prompt's settings",
"type": "object",
"properties": {
"format": {
"title": "format settings",
"description": "Format settings",
"type": "array",
"uniqueItems": true,
"items": {
"type": "object",
"properties": {
"show": {
"description": "Whether to show an item",
"type": "boolean",
"default": true
},
"kind": {
"description": "A kind of an item",
"type": "string",
"enum": [
"current-time",
"current-device-name",
"current-user-name",
"current-path",
"last-command-status"
]
},
"colors": {
"title": "item color settings",
"description": "Item color settings",
"type": "object",
"properties": {
"foreground": {
"description": "An item foreground color",
"$ref": "#/definitions/color",
"default": "black"
},
"background": {
"description": "An item background color",
"$ref": "#/definitions/color",
"default": "white"
}
},
"additionalProperties": false
},
"format": {
"type": "string",
"default": "[%s]"
}
},
"required": ["kind"],
"additionalProperties": false
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
but it is being converted as:
const schema = z.object({
$schema: z.string(),
title: z.string(),
description: z.string(),
definitions: z.object({ // I am not sure about it but I guess that `definitions` should be inlined where they are used.
color: z.object({ type: z.string(), enum: z.array(z.string()) }), // `type`, `enum` are not keys themselves in JSON schemas, they are restrictions applied to surrounding property (in this case to `color`).
}),
type: z.string(),
properties: z.object({
messages: z.object({
title: z.string(),
description: z.string(),
type: z.string(),
properties: z.object({
info: z.object({
title: z.string(),
description: z.string(),
type: z.string(),
properties: z.object({
foreground: z.object({ description: z.string(), $ref: z.string() }),
background: z.object({ description: z.string(), $ref: z.string() }),
}),
additionalProperties: z.boolean(), // Again, it's not a key available to the end user, it's a constraint telling that no other properties are allowed except `foreground` and `background` here.
}),
warn: z.object({
title: z.string(),
description: z.string(),
type: z.string(),
properties: z.object({
foreground: z.object({ description: z.string(), $ref: z.string() }),
background: z.object({ description: z.string(), $ref: z.string() }),
}),
additionalProperties: z.boolean(),
}),
error: z.object({
title: z.string(),
description: z.string(),
type: z.string(),
properties: z.object({
foreground: z.object({ description: z.string(), $ref: z.string() }),
background: z.object({ description: z.string(), $ref: z.string() }),
}),
additionalProperties: z.boolean(),
}),
}),
additionalProperties: z.boolean(),
}),
prompt: z.object({
title: z.string(),
description: z.string(),
type: z.string(),
properties: z.object({
format: z.object({
title: z.string(),
description: z.string(),
type: z.string(),
uniqueItems: z.boolean(),
items: z.object({
type: z.string(),
properties: z.object({
show: z.object({
description: z.string(),
type: z.string(),
default: z.boolean(),
}),
kind: z.object({
description: z.string(),
type: z.string(),
enum: z.array(z.string()),
}),
colors: z.object({
title: z.string(),
description: z.string(),
type: z.string(),
properties: z.object({
foreground: z.object({
description: z.string(),
$ref: z.string(),
default: z.string(),
}),
background: z.object({
description: z.string(),
$ref: z.string(),
default: z.string(),
}),
}),
additionalProperties: z.boolean(),
}),
format: z.object({ type: z.string(), default: z.string() }),
}),
required: z.array(z.string()),
additionalProperties: z.boolean(),
}),
}),
}),
additionalProperties: z.boolean(),
}),
}),
additionalProperties: z.boolean(),
});
Maybe my expectations are wrong but when I hear JSON schema -> smth
conversion I expect correct interpretation of JSON schema according to the specified draft (7 in this case). If there is already some tool that does what I expect please tell me it's name.
I have this library successfully converting the json schema to a stringified representation of a Zod schema, but am at a loss on how to actually parse something with it. I'm trying to dynamically take a JSON Schema via payload argument, convert it to Zod, and use it to validate all in one web request.
My first thought would be something like eval
, but that doesn't seem great from a security standpoint.
Given this JSON Schema:
{
type: "object",
properties: {
s: {
type: "string",
const: ""
}
}
}
This result is expected (z.literal("")
):
import { z } from "zod";
export default z.object({ s: z.literal("").optional() });
However the actual result is (z.string()
):
import { z } from "zod";
export default z.object({ s: z.string().optional() });
It seems json-schema-to-zod
outputs the incorrect result because the string literal "" is falsey. It seems this is a problem for all falsey values.
The output here appears to be a string defining Zod schema, however I don't always know the JSONschema I am getting ahead of time and it would be incredibly useful to be able to get a zod schema at runtime rather than a string that needs to be put into a file at compile time.
๐ I'm trying out this library in conjunction with https://github.com/StefanTerdell/zod-to-json-schema.
It'd be great if this library would support errorMessages in the json schema that are inserted by zod-to-json-schema
Thanks for such a cool pair of libs!
Example:
echo '{"title": "Foo Bar", "type": "string", "description": "\"\" means the default."}' | json-schema-to-zod -s /dev/stdin
Could use improved documentation on this scenario. Is an error thrown? Is a z.any()
schema output, as implied by this issue and a quick test I just ran?
My use case is that I'm trying to allow consumers of my API to specify a JSON schema as part of their payload, which I then parse with this library and use it to parse something before I return it to them.
Hello Stefan, first of all congrats for your really helpful lib!
It would be great to add the missing string format validations that are part of JSON format assertion vocabulary, maybe starting from the ones that maps easily with zod assertions, such as IP Addresses validations.
That could only require a small change to src/parsers/parseString.ts
module:
import { JSONSchema7 } from "json-schema";
export const parseString = (schema: JSONSchema7 & { type: "string" }) => {
let r = "z.string()";
if (schema.pattern)
r += `.regex(new RegExp(${JSON.stringify(schema.pattern)}))`;
if (schema.format === "email") r += ".email()";
+ if (schema.format === "ip") r += ".ip()";
+ if (schema.format === "ipv4") r += ".ip({ version: "v4" })";
+ if (schema.format === "ipv6") r += ".ip({ version: "v6" })";
else if (schema.format === "uri") r += ".url()";
else if (schema.format === "uuid") r += ".uuid()";
else if (schema.format === "date-time") r += ".datetime()";
if (typeof schema.minLength === "number") r += `.min(${schema.minLength})`;
if (typeof schema.maxLength === "number") r += `.max(${schema.maxLength})`;
return r;
};
Thank you!
Hello! Thank you for your hard work on this, it's a really great tool.
I'm interested in doing something very similar for generating io-ts
codecs and have been fiddling with modifying the parse*.ts
files to support generic code generation implementations, e.g. a user could choose between zod
, io-ts
, or any other option as long as they can conform to the required implementation interface.
Would you consider a pull request with this change or does it go too far outside the relevant scope?
JSON schema:
type: object
properties:
id:
type: integer
required: true
username:
type: string
required: true
status:
type: string
required: true
required: true
Error:
/Users/gajus/Documents/dev/contra/contra-api/node_modules/json-schema-to-zod/parsers/parseObject.js:15
return `${JSON.stringify(k)}:${(0, parseSchema_1.parseSchema)(v)}${((_a = schema.required) === null || _a === void 0 ? void 0 : _a.includes(k)) ? requiredFlag : ".optional()"}`;
^
TypeError: _a2.includes is not a function
at <anonymous> (/Users/gajus/Documents/dev/contra/contra-api/node_modules/json-schema-to-zod/parsers/parseObject.js:15:144)
at Array.map (<anonymous>)
at parseObject (/Users/gajus/Documents/dev/contra/contra-api/node_modules/json-schema-to-zod/parsers/parseObject.js:13:149)
at selectParser (/Users/gajus/Documents/dev/contra/contra-api/node_modules/json-schema-to-zod/parsers/parseSchema.js:34:34)
at parseSchema (/Users/gajus/Documents/dev/contra/contra-api/node_modules/json-schema-to-zod/parsers/parseSchema.js:22:18)
at jsonSchemaToZod (/Users/gajus/Documents/dev/contra/contra-api/node_modules/json-schema-to-zod/jsonSchemaToZod.js:14:217)
at <anonymous> (/Users/gajus/Documents/dev/contra/contra-api/src/bin/generate-zod.ts:45:15)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
The API is stable enough to release major 1, but before that happens we need to have a decent level of tests. I've added a basic jest setup and added a single test to set the pattern, but I have very limited time to work on this myself, so feel free to add to it. Please reference this issue in your PRs. Once this issue has been resolved I will consider releasing 1.0.0.
There is some issue with 'allOf'
Schema:
"someProperty": { "description": "ABCD", "type": "object", "allOf": [ { "type": "object", "properties": { "entryId": { "description": "id", "type": "string", "example": "VYP3" }, ... }, "required": ["entryId"] }, { "type": "object", "properties": { "otherValues": { "type": "array", "items": { "type": "object", "properties": {...}, "required": ["value", "key"] } }, ... } } ], },
Result:
someProperty: z.record(z.any()).describe("ABCD").optional(),
I probably find where the problem is. It is in 'selectParser' function, where code goes to isObject way and 'allOf' else if is not hit at all.
Hi, I appreciate the simplicity of this library. But without support for resolving $ref's, it makes it quite limited for anything other than simple schemas. Have you considered adding support for $ref?
Thanks :)
I just installed this package, and pasted the example code from README.md into a .ts
file in my project:
import {
jsonSchemaToZod,
jsonSchemaToZodDereffed,
parseSchema,
} from "json-schema-to-zod";
const myObject = {
type: "object",
properties: {
hello: {
type: "string",
},
},
};
const module = jsonSchemaToZod(myObject);
const dereffed = await jsonSchemaToZodDereffed(myObject);
const schema = parseSchema(myObject);
The last 3 lines all give TS errors:
Argument of type '{ type: string; properties: { hello: { type: string; }; }; }' is not assignable to parameter of type 'JSONSchema7'.
Types of property 'type' are incompatible.
Type 'string' is not assignable to type 'JSONSchema7TypeName | JSONSchema7TypeName[] | undefined'.
Argument of type '{ type: string; properties: { hello: { type: string; }; }; }' is not assignable to parameter of type 'JSONSchema7'.
Argument of type '{ type: string; properties: { hello: { type: string; }; }; }' is not assignable to parameter of type 'boolean | JSONSchema7'.
Type '{ type: string; properties: { hello: { type: string; }; }; }' is not assignable to type 'JSONSchema7'.
It works ok at runtime though.
I also tried on something larger, i.e. replaced const myObject = {...}` value with this schema. ... same thing.
Hello,
I'm having an issue converting a relatively simple JSON schema using the CLI, however the same schema gets converted correctly on the demo site.
I've condensed the schema down to a minimum failing example, which you can find below:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/Bestiary",
"definitions": {
"Bestiary": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}
}
}
This results in an output of export const schema = z.any();
It's quite possible that i'm missing something, but using simple object based schemas that don't use $ref
and definitions
seems to work fine on the CLI.
Hi,
Great project!
I'm trying to parse the FHIR JSON schema (attached fhir.schema.zip)
But the output is much smaller than anticpiated.
I'm very new to Zod and FHIR so I'm sure this is user error... but the results of jsonSchemaToZod
is "just":
import { z } from "zod";
export default z
.any()
.superRefine((x, ctx) => {
const schemas = [
z.any(),
z.any(),
// ...
];
const errors = schemas.reduce(
(errors: z.ZodError[], schema) =>
((result) => ("error" in result ? [...errors, result.error] : errors))(
schema.safeParse(x)
),
[]
);
if (schemas.length - errors.length !== 1) {
ctx.addIssue({
path: ctx.path,
code: "invalid_union",
unionErrors: errors,
message: "Invalid input: Should pass single schema",
});
}
})
.describe(
"see http://hl7.org/fhir/json.html#schema for information about the FHIR Json Schemas"
);
Any help gratefully received.
Thanks again for a great project.
Consider the following type DateRangeExtended
:
type DateRange = { from?: number; to?: number };
enum DateRangeType {
today = 1,
yesterday = 2,
tomorrow = 3
}
export type DateRangeExtended = DateRange | { type: DateRangeType };
The generated zod schema:
import { z } from "zod";
export default z
.union([
z
.object({
from: z.number().optional(),
to: z.number().optional(),
})
.catchall(z.never()),
z
.object({
type: z.union([
z.literal(1),
z.literal(2),
z.literal(3)
]),
})
.catchall(z.never()),
])
Now when the data is parsed I get an empty object because DateRange
allows empty properties and other properties are not prohibited by generated schema:
const result = schema.safeParse({ type: 1 });
if (result.success) {
console.log(result.data); // => { }
}
It would be useful if this library supported strict option.
Hi, I'm trying to update to 1.0+, and my code has something like this:
import { parseSchema } from 'json-schema-to-zod';
// ...
// Convert to Zod
const zodSchema = parseSchema(jsonSchema);
As of 1.0.0, I'm seeing a type error that parseSchema requires a second argument.
In the README in the repo here, the sample code seems to just call parseSchema with only one argument. Do you have a code sample for how to pass the refs argument? Or, is it possible to make it so that parseSchema can be called with no arguments?
Let me know if I can help!
First, thank you for this library!
Currently, "nullable": true
is not parsed, even though there is z.nullable()
in zod.
I know we can represent null
in Json-Schema using a primitive value (similar to javascript) such as:
"type": ["string", "null"]
.
input:
{
"properties": {
"name": {
"type": ["string", "null"]
},
"surname": {
"type": "string",
"nullable": true,
},
},
"required": ["name", "surname"],
"title": "Person",
"type": "object"
}
output:
const schema = z.object({
name: z.union([z.string(), z.null()]),
surname: z.string(),
});
But my use-case is that an OpenAPI backend is generating the Json-schemas for the payloads I get in the front-end.
And, from my understanding, OpenAPI happens to consider null
as a value, and not a type.
So it generates Json-Schemas with this "nullable"
field, which features in their "extended subset of JSON Schema Specification".
I'm still learning Typescript, but I think json-schema-to-zod
currently uses JSONSchema7
coming from @types/json-schema
, which does not mention this "nullable"
field.
I'm not sure how I should go about that.
I've reviewed other posts on the same issue, but am unable to convert / eval the string zod schema.
const zod_schema = eval(parseSchema(schema)) ;
results in ReferenceError: z is not defined
I also tried:const import_z = 'import { z } from "zod";'
const combined = import_z + parseSchema(schema);
This resulted in the same error.
const zod_schema = eval(jsonSchemaToZod(schema)) ;
results in SyntaxError: Cannot use import statement outside a module
{
"type": "object",
"properties": {
"myString": {
"type": "string",
"minLength": 5,
"errorMessage": {
"minLength": "Minimum 5 required"
}
},
"myUnion": {
"type": [
"number",
"boolean"
]
}
},
"required": [
"myString",
"myUnion"
],
"additionalProperties": false,
"description": "My neat object schema",
"$schema": "http://json-schema.org/draft-07/schema#"
}
z
.object({
myString: z.string().min(5),
myUnion: z.union([z.number(), z.boolean()]),
})
.catchall(z.never())
.describe("My neat object schema");
Can help make sure people are including the information you need them to. Good guide on doing it here: https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository
Would it be possible to remove the dependencies that use node built-in modules like process
?
Simply importing json-schema-to-zod
results in an error:
ReferenceError: process is not defined at node_modules/.pnpm/@[email protected]/node_modules/@apidevtools/json-schema-ref-parser/lib/util/url.js (url.js:3:29) at __require (chunk-J43GMYXM.js?v=f53e2eac:11:50) at node_modules/.pnpm/@[email protected]/node_modules/@apidevtools/json-schema-ref-parser/lib/pointer.js (pointer.js:6:13) at __require (chunk-J43GMYXM.js?v=f53e2eac:11:50) at node_modules/.pnpm/@[email protected]/node_modules/@apidevtools/json-schema-ref-parser/lib/ref.js (ref.js:5:17) at __require (chunk-J43GMYXM.js?v=f53e2eac:11:50) at node_modules/.pnpm/@[email protected]/node_modules/@apidevtools/json-schema-ref-parser/lib/refs.js (refs.js:4:14) at __require (chunk-J43GMYXM.js?v=f53e2eac:11:50) at node_modules/.pnpm/@[email protected]/node_modules/@apidevtools/json-schema-ref-parser/lib/index.js (index.js:4:15) at __require (chunk-J43GMYXM.js?v=f53e2eac:11:50)
This is because some dependency is checking the platform via process
:
// url.js
let isWindows = /^win/.test(process.platform),
I was able to get json-schema-to-zod
working in Parcel via its node emulation, but it required some extra steps: parcel-bundler/parcel#7697 (comment)
I was unable to get similar node emulation working in SvelteKit/Vite. Perhaps because the dependencies don't explicitly import process
so Parcel is doing some extra magic.
I am curious how the online demo (https://stefanterdell.github.io/json-schema-to-zod-react/) does not result in the same error. Does this also use node emulation, or was it built with versions of dependencies that didn't use process
?
Prefacing this by making it clear that i'm aware on your docs you provide the command with NPM and not Yarn but I feel like many people will have a preference and will try to do it with Yarn regardless.
When installing the package via yarn global add json-schema-to-zod
and attempting to run any associated command with it I get an error env: node\r: No such file or directory
. This is due to the fact that NPM auto converts line endings but Yarn does not and requires a bit of help.
The fix for this was to use npx to convert after install like so npx crlf --set=LF /path/to/json-schema-to-zod
Hi @StefanTerdell,
Unfortunately, mine is just a suggestion -- I don't have the bandwidth currently to open a PR (sorry about this).
However, when trying out the package today, I noticed that I'm getting a TS error when providing the JSON schema that ajv accepts & compiles correctly.
Reproduction here.
So I'm wondering if accepting ajv's input types for the schema could be part of the work you suggested above.
Originally posted by @p6l-richard in #2 (comment)
Hey there,
I think since version 1.0.0 an object with additionalProperties: false
will generate a schema which sets .catchall(z.never())
on the object.
When inferring the TS type from the schema this turns into a union type like this:
type SchemaType = {
myString: string;
myUnion: number | boolean;
} & {
[k: string]: never;
}
which then leads to the following error when using this type:
Type '{ myString: string; myUnion: true; }' is not assignable to type 'objectOutputType<{ myString: ZodString; myUnion: ZodUnion<[ZodNumber, ZodBoolean]>; }, ZodNever, "strip">'.
Type '{ myString: string; myUnion: true; }' is not assignable to type '{ [k: string]: never; }'.
Property 'myString' is incompatible with index signature.
Type 'string' is not assignable to type 'never'.ts(2322)
Previously the object would be marked as .strict()
which worked perfectly fine.
Is this expected behavior and I'm holding it wrong? ๐ Or is it a bug?
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/mySchema",
"definitions": {
"mySchema": {
"description": "My neat object schema",
"type": "object",
"properties": {
"myString": {
"type": "string",
"minLength": 5
},
"myUnion": {
"type": ["number", "boolean"]
}
},
"additionalProperties": false,
"required": ["myString", "myUnion"]
}
}
}
import { z } from 'zod';
const schema = z
.object({
myString: z.string().min(5),
myUnion: z.union([z.number(), z.boolean()]),
})
.catchall(z.never())
.describe('My neat object schema');
type SchemaType = z.infer<typeof schema>;
const obj: SchemaType = {
myString: 'asdfs',
myUnion: true,
};
Is it possible to use URL like https://api.myapp.com/json as source?
I compiled a zod validator from a json schema using roughly this snippet:
import jsonSchemaToZod from "json-schema-to-zod";
const schema = await import("schema.json");
const asZod = jsonSchemaToZod(schema);
await writeFile("output.ts");
here's a gist with the 2 other files (for brevity in this issue description) https://gist.github.com/cdmistman/c73e1c66ee02e21235bac109c1a354ce
note that the generated default value is invalid; typescript returns the following error:
output.ts:7:5 - error TS2769: No overload matches this call.
Overload 2 of 2, '(def: () => objectInputType<{ insert: ZodObject<{ string: ZodString; }, "strip", ZodTypeAny, { string: string; }, { string: string; }>; }, ZodNever, "strip">): ZodDefault<...>', gave the following error.
Argument of type '{ $schema: string; title: string; oneOf: { type: string; required: string[]; properties: { insert: { type: string; required: string[]; properties: { string: { type: string; }; }; }; }; additionalProperties: boolean; }[]; }' is not assignable to parameter of type '() => objectInputType<{ insert: ZodObject<{ string: ZodString; }, "strip", ZodTypeAny, { string: string; }, { string: string; }>; }, ZodNever, "strip">'.
Object literal may only specify known properties, and '$schema' does not exist in type '() => objectInputType<{ insert: ZodObject<{ string: ZodString; }, "strip", ZodTypeAny, { string: string; }, { string: string; }>; }, ZodNever, "strip">'.
7 $schema: "http://json-schema.org/draft-07/schema#",
~~~~~~~
i'd at least expect an error if a valid default value can be generated! it would also be nice to report in the error that withoutDefaults: true
should be passed as an option to jsonSchemaToZod
Gives people a better idea of how to contribute, and makes the project more approachable.
Passing the converted JSON Schema from the sister package to this gives out z.any() always. attaching a screenshot which tries to convert the example from https://www.npmjs.com/package/zod-to-json-schema directly on demo app, it produces the same result.
It would be helpful to be able to customize parsing when needed, i.e. to allow coercing strings to numbers or other less standard behaviour.
My initial PR was reject but I'm happy to rework this if there's an interface you would be open to.
#39
I'm trying to create a schema based on the package.json schema, and it has some problems.
The online demo gives InternalError: too much recursion
.
Running the cli on it with no "--deref" isn't useful, since it gives things like dependencies: z.any()
when it should be a z.object()
or z.record()
.
Running the cli on it with "--deref" gives RangeError: Maximum call stack size exceeded
It's not a show stopper, but it would be nice to use .prettierrc
if it exists
When trying to interact with the web demo, it seems like it may have broken with the new version?
It still accepts a json, but the zod type is never generated as a result.
The npm
registry page here has no documentation: https://www.npmjs.com/package/json-schema-to-zod
However, this library's counterpart seems to have it: https://www.npmjs.com/package/zod-to-json-schema
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.