zod-prisma-types
zod-prisma-types copied to clipboard
Json field types incompatible with generated Prisma types
Minimal reproduction:
schema.prisma
generator client {
provider = "prisma-client-js"
}
generator zod {
provider = "zod-prisma-types"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Model {
id String @id @default(uuid())
json Json
}
index.ts
import type { Model as PrismaModel } from '@prisma/client';
import type { Model as ZodModel } from './generated/zod';
const a: PrismaModel = { id: 'abc', json: {} };
const b: ZodModel = a;
This results in the TypeScript error:
Type 'Model' is not assignable to type '{ id: string; json: InputJsonValue; }'.
Types of property 'json' are incompatible.
Type 'JsonValue' is not assignable to type 'InputJsonValue'.
Type 'null' is not assignable to type 'InputJsonValue'.
If the field is specified as json Json?
, the error is:
Type 'Model' is not assignable to type '{ json?: string | number | true | JsonObject | JsonArray | DbNull | JsonNull | undefined; id: string; }'.
Types of property 'json' are incompatible.
Type 'JsonValue' is not assignable to type 'string | number | true | JsonObject | JsonArray | DbNull | JsonNull | undefined'.
Type 'null' is not assignable to type 'string | number | true | JsonObject | JsonArray | DbNull | JsonNull | undefined'.
My pull request https://github.com/chrishoermann/zod-prisma-types/pull/135 fixes these issues, but may not be taking all use cases into account.
@jaschaephraim thanks for the report. I looked into it a bit and I'm not quite sure why prisma did it this way. Because when inspecting the prisma type JsonValue
it looks like this:
export type JsonValue = string | number | boolean | JsonObject | JsonArray | null
so according to this type the json field can be nullable even if it is marked as non nullable. So if we have a model like this
model JsonModel {
id Int @id @default(autoincrement())
json Json
jsonOpt Json?
}
both, the json
and jsonOpt
field could be null
, which is, at least to my understanding, not what the schema tells me.
interestingly the generated prisma type is
export type JsonModel = {
id: number
json: Prisma.JsonValue // is nullable - should not be
jsonOpt: Prisma.JsonValue | null // added "null" where null is already included in "JsonValue"
}
so the following would be ok for prisma even if, in my opinon, it should not:
const a: JsonModelPrisma = { id: 1, json: {}, jsonOpt: null }; // is ok - as expected from looking at the schema
const a: JsonModelPrisma = { id: 1, json: null, jsonOpt: null }; // is also ok - not as expected from looking at the schema
I must admit, that I do not have much experience using json fields with prisma - so maybe you have some more insight if this is all ok what they did or not.
problem with generator
So the easiest way to fix this in the generator would be to just add null
to the JsonValue
which then sould be used instead of InputJsonValue
(like in your PR) and NullableJsonValue
. This would exactly replicate the prisma type and fix the typescript issue but it would prevent an acutall null
to be converted to DbNull
or JsonNull
which is achieved via transformJsonNull
.
I'm actually not sure now why i did it this way in the first place but I think I wanted to be able to validate DbNull
or JsonNull
via trpc or it was a bug/feature someone reported.
I'll also post an issue on the prisma page to get some insight why they did it this way. I'll do some further experimenting and then address your PR.
I found an issue in the prisma repo that is about the behaviour I mentioned and it seems that json fields can contain null
when beeing retrieved from the database. So the type above is valid and I must rethink the way I implemented it - because actually my transformJsonNull
should be used in create
or update
methods (I think).
I found an issue in the prisma repo that is about the behaviour I mentioned and it seems that json fields can contain
null
when beeing retrieved from the database. So the type above is valid and I must rethink the way I implemented it - because actually mytransformJsonNull
should be used increate
orupdate
methods (I think).
Have you found a solution for create
and update
? I have the same problem. On create
, I don't want JsonNullValueInputSchema
.
If I try to override with this:
prompt Json /// @zod.custom.use(z.record(z.nativeEnum(PrompLanguages), z.string()))
I have this result:
prompt: z.union([z.lazy(() => JsonNullValueInputSchema), z.lazy(() => z.record(z.nativeEnum(PrompLanguages), z.string()))]),
I would like:
prompt: z.record(z.nativeEnum(PrompLanguages), z.string())),
Any news on this? Or is there any workaround now?
@jaschaephraim in v3.0.0
I revamped the json implementation so it exactly matches the types generated by prisma.
for a given model like
model JsonModel {
id Int @id @default(autoincrement())
json Json
jsonOpt Json?
}
the following schemas are created
// PRISMA TYPES
// ------------------------------------------------------
export type JsonValue = string | number | boolean | JsonObject | JsonArray | null
export type JsonModel = $Result.DefaultSelection<Prisma.$JsonModelPayload>;
export type $JsonModelPayload<
ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs,
> = {
name: 'JsonModel';
objects: {};
scalars: $Extensions.GetPayloadResult<
{
id: number;
json: Prisma.JsonValue;
jsonOpt: Prisma.JsonValue | null;
},
ExtArgs['result']['jsonModel']
>;
composites: {};
};
// SCHEMA
// ------------------------------------------------------
export const JsonValueSchema: z.ZodType<Prisma.JsonValue> = z.lazy(() =>
z.union([
z.string(),
z.number(),
z.boolean(),
z.literal(null),
z.record(z.lazy(() => JsonValueSchema.optional())),
z.array(z.lazy(() => JsonValueSchema)),
]),
);
export const JsonModelSchema = z.object({
id: z.number().int(),
json: JsonValueSchema.nullable(), // just a nullable json input like in prismas type
jsonOpt: JsonValueSchema,
});
// INPUT TYPES
// ------------------------------------------------------
export const JsonNullValueInput: {
JsonNull: typeof JsonNull;
};
export type JsonNullValueInput =
(typeof JsonNullValueInput)[keyof typeof JsonNullValueInput];
export const NullableJsonNullValueInput: {
DbNull: typeof DbNull;
JsonNull: typeof JsonNull;
};
export type NullableJsonNullValueInput =
(typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput];
export type JsonModelCreateInput = {
json: JsonNullValueInput | InputJsonValue;
jsonOpt?: NullableJsonNullValueInput | InputJsonValue;
};
// EXAMPLE INPUT SCHEMA
// ------------------------------------------------------
export const JsonNullValueInputSchema = z
.enum(['JsonNull'])
.transform((value) => (value === 'JsonNull' ? Prisma.JsonNull : value));
export const InputJsonValueSchema: z.ZodType<Prisma.InputJsonValue> = z.lazy(
() =>
z.union([
z.string(),
z.number(),
z.boolean(),
z.object({ toJSON: z.function(z.tuple([]), z.any()) }),
z.record(z.lazy(() => z.union([InputJsonValueSchema, z.literal(null)]))),
z.array(z.lazy(() => z.union([InputJsonValueSchema, z.literal(null)]))),
]),
);
export const JsonModelCreateInputSchema: z.ZodType<Prisma.JsonModelCreateInput> =
z
.object({
json: z.union([
z.lazy(() => JsonNullValueInputSchema),
InputJsonValueSchema,
]), // complex input that can handle passed in `DbNull` of `JsonNull` strings
jsonOpt: z
.union([
z.lazy(() => NullableJsonNullValueInputSchema),
InputJsonValueSchema,
])
.optional(),
})
.strict();
I think this should fix the problem menitoned by @jaschaephraim.
In @Jeromearsene's case I think the generated schema with the .custom.use()
directive is actually valid since the field, even if it is not nullable, can accept a JsonNull
value since this would be valid json. The only thing that would not be possible is to write the string "JsonValue" to the json field as typeof string. But I don't assume that this would be a real life use case. 😉
I am seeing a similar issue but with toJSON()
export type InputJsonValue = string | number | boolean | InputJsonObject | InputJsonArray | { toJSON(): unknown }
../../libs/shared/prisma-zod/src/lib/generated/inputTypeSchemas/InputJsonValueSchema.ts:4:14 - error TS2322: Type 'ZodLazy<ZodUnion<[ZodString, ZodNumber, ZodBoolean, ZodObject<{ toJSON: ZodFunction<ZodTuple<[], null>, ZodAny>; }, "strip", ZodTypeAny, { ...; }, { ...; }>, ZodRecord<...>, ZodArray<...>]>>' is not assignable to type 'ZodType<InputJsonValue, ZodTypeDef, InputJsonValue>'.
Types of property '_type' are incompatible.
Type 'string | number | boolean | any[] | Record<string, any> | { toJSON?: (...args: unknown[]) => any; }' is not assignable to type 'InputJsonValue'.
Type '{ toJSON?: (...args: unknown[]) => any; }' is not assignable to type 'InputJsonValue'.
Type '{ toJSON?: (...args: unknown[]) => any; }' is not assignable to type '{ toJSON(): unknown; }'.
Property 'toJSON' is optional in type '{ toJSON?: (...args: unknown[]) => any; }' but required in type '{ toJSON(): unknown; }'.
4 export const InputJsonValueSchema: z.ZodType<Prisma.InputJsonValue> = z.lazy(() =>
@jamespsterling for this type error to go away you have to set strictNullChecks: true
in your tsconfig.json
.
@jaschaephraim in
v3.0.0
I revamped the json implementation so it exactly matches the types generated by prisma.for a given model like
model JsonModel { id Int @id @default(autoincrement()) json Json jsonOpt Json? }
the following schemas are created
// PRISMA TYPES // ------------------------------------------------------ export type JsonValue = string | number | boolean | JsonObject | JsonArray | null export type JsonModel = $Result.DefaultSelection<Prisma.$JsonModelPayload>; export type $JsonModelPayload< ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs, > = { name: 'JsonModel'; objects: {}; scalars: $Extensions.GetPayloadResult< { id: number; json: Prisma.JsonValue; jsonOpt: Prisma.JsonValue | null; }, ExtArgs['result']['jsonModel'] >; composites: {}; }; // SCHEMA // ------------------------------------------------------ export const JsonValueSchema: z.ZodType<Prisma.JsonValue> = z.lazy(() => z.union([ z.string(), z.number(), z.boolean(), z.literal(null), z.record(z.lazy(() => JsonValueSchema.optional())), z.array(z.lazy(() => JsonValueSchema)), ]), ); export const JsonModelSchema = z.object({ id: z.number().int(), json: JsonValueSchema.nullable(), // just a nullable json input like in prismas type jsonOpt: JsonValueSchema, }); // INPUT TYPES // ------------------------------------------------------ export const JsonNullValueInput: { JsonNull: typeof JsonNull; }; export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput]; export const NullableJsonNullValueInput: { DbNull: typeof DbNull; JsonNull: typeof JsonNull; }; export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput]; export type JsonModelCreateInput = { json: JsonNullValueInput | InputJsonValue; jsonOpt?: NullableJsonNullValueInput | InputJsonValue; }; // EXAMPLE INPUT SCHEMA // ------------------------------------------------------ export const JsonNullValueInputSchema = z .enum(['JsonNull']) .transform((value) => (value === 'JsonNull' ? Prisma.JsonNull : value)); export const InputJsonValueSchema: z.ZodType<Prisma.InputJsonValue> = z.lazy( () => z.union([ z.string(), z.number(), z.boolean(), z.object({ toJSON: z.function(z.tuple([]), z.any()) }), z.record(z.lazy(() => z.union([InputJsonValueSchema, z.literal(null)]))), z.array(z.lazy(() => z.union([InputJsonValueSchema, z.literal(null)]))), ]), ); export const JsonModelCreateInputSchema: z.ZodType<Prisma.JsonModelCreateInput> = z .object({ json: z.union([ z.lazy(() => JsonNullValueInputSchema), InputJsonValueSchema, ]), // complex input that can handle passed in `DbNull` of `JsonNull` strings jsonOpt: z .union([ z.lazy(() => NullableJsonNullValueInputSchema), InputJsonValueSchema, ]) .optional(), }) .strict();
I think this should fix the problem menitoned by @jaschaephraim.
In @Jeromearsene's case I think the generated schema with the
.custom.use()
directive is actually valid since the field, even if it is not nullable, can accept aJsonNull
value since this would be valid json. The only thing that would not be possible is to write the string "JsonValue" to the json field as typeof string. But I don't assume that this would be a real life use case. 😉
Would be useful to have a way of disabling union on json field when using custom.use to force using just the passed zod object. We're defining specific types using another prisma extension.