Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions packages/orm/src/client/crud/dialects/sqlite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,27 +43,31 @@ export class SqliteCrudDialect<Schema extends SchemaDef> extends BaseCrudDialect
invariant(false, 'should not reach here: AnyNull is not a valid input value');
}

// Handle JSON type before array check - JSON values should be stringified as a whole
if (type === 'Json') {
return JSON.stringify(value);
}

// Handle typed JSON field before array check
if (this.schema.typeDefs && type in this.schema.typeDefs) {
return JSON.stringify(value);
}

if (Array.isArray(value)) {
return value.map((v) => this.transformPrimitive(v, type, false));
} else {
if (this.schema.typeDefs && type in this.schema.typeDefs) {
// typed JSON field
return JSON.stringify(value);
} else {
return match(type)
.with('Boolean', () => (value ? 1 : 0))
.with('DateTime', () =>
value instanceof Date
? value.toISOString()
: typeof value === 'string'
? new Date(value).toISOString()
: value,
)
.with('Decimal', () => (value as Decimal).toString())
.with('Bytes', () => Buffer.from(value as Uint8Array))
.with('Json', () => JSON.stringify(value))
.otherwise(() => value);
}
return match(type)
.with('Boolean', () => (value ? 1 : 0))
.with('DateTime', () =>
value instanceof Date
? value.toISOString()
: typeof value === 'string'
? new Date(value).toISOString()
: value,
)
.with('Decimal', () => (value as Decimal).toString())
.with('Bytes', () => Buffer.from(value as Uint8Array))
.otherwise(() => value);
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/orm/src/client/crud/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ export class InputValidator<Schema extends SchemaDef> {

const schema = z.union([
...options,
z.lazy(() => this.makeJsonValueSchema(false, false).array()),
z.lazy(() => z.union([this.makeJsonValueSchema(false, false), z.null()]).array()),
z.record(
z.string(),
z.lazy(() => z.union([this.makeJsonValueSchema(false, false), z.null()])),
Expand Down
75 changes: 75 additions & 0 deletions tests/e2e/orm/client-api/json-filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,79 @@ model PlainJson {
});
await expect(db.plainJson.create({ data: { data: DbNull } })).toBeRejectedByValidation();
});

it('works with JSON objects containing null values', async () => {
const db = await createTestClient(schema);

// Create a record with an object containing a null property value
const rec1 = await db.plainJson.create({ data: { data: { key: null } } });
expect(rec1.data).toEqual({ key: null });

// Create a record with nested object containing null values
const rec2 = await db.plainJson.create({ data: { data: { outer: { inner: null }, valid: 'value' } } });
expect(rec2.data).toEqual({ outer: { inner: null }, valid: 'value' });

// Query with equality filter for object with null value
await expect(
db.plainJson.findFirst({ where: { data: { equals: { key: null } } } }),
).resolves.toMatchObject({
id: rec1.id,
data: { key: null },
});

// Query with equality filter for nested object with null value
await expect(
db.plainJson.findFirst({ where: { data: { equals: { outer: { inner: null }, valid: 'value' } } } }),
).resolves.toMatchObject({
id: rec2.id,
data: { outer: { inner: null }, valid: 'value' },
});

// Query with not filter for object with null value
const notResults = await db.plainJson.findMany({
where: { data: { not: { key: null } } },
});
expect(notResults.find((r) => r.id === rec1.id)).toBeUndefined();
expect(notResults.find((r) => r.id === rec2.id)).toBeDefined();
});

it('works with JSON arrays containing null values', async () => {
const db = await createTestClient(schema);

// Create a record with an array containing null values
const rec1 = await db.plainJson.create({ data: { data: [1, null, 3] } });
expect(rec1.data).toEqual([1, null, 3]);

// Create a record with an array of objects including null
const rec2 = await db.plainJson.create({ data: { data: [{ a: 1 }, null, { b: 2 }] } });
expect(rec2.data).toEqual([{ a: 1 }, null, { b: 2 }]);

// Create a record with nested arrays containing null
const rec3 = await db.plainJson.create({ data: { data: [[1, null], [null, 2]] } });
expect(rec3.data).toEqual([[1, null], [null, 2]]);

// Query with equality filter for array with null value
await expect(
db.plainJson.findFirst({ where: { data: { equals: [1, null, 3] } } }),
).resolves.toMatchObject({
id: rec1.id,
data: [1, null, 3],
});

// Query with equality filter for array of objects with null
await expect(
db.plainJson.findFirst({ where: { data: { equals: [{ a: 1 }, null, { b: 2 }] } } }),
).resolves.toMatchObject({
id: rec2.id,
data: [{ a: 1 }, null, { b: 2 }],
});

// Query with not filter for array with null value
const notResults = await db.plainJson.findMany({
where: { data: { not: [1, null, 3] } },
});
expect(notResults.find((r) => r.id === rec1.id)).toBeUndefined();
expect(notResults.find((r) => r.id === rec2.id)).toBeDefined();
expect(notResults.find((r) => r.id === rec3.id)).toBeDefined();
});
});