使用 Json 字段
使用 Json Prisma ORM 字段类型,可以读取、写入和对底层数据库中的 JSON 类型执行基本筛选。在以下示例中,User 模型有一个可选的 Json 字段,名为 extendedPetsData
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
extendedPetsData Json?
}
字段值示例
{
"pet1": {
"petName": "Claudine",
"petType": "House cat"
},
"pet2": {
"petName": "Sunny",
"petType": "Gerbil"
}
}
Json 字段支持一些额外的类型,例如 string 和 boolean。这些额外的类型旨在匹配 JSON.parse() 支持的类型
export type JsonValue =
| string
| number
| boolean
| null
| JsonObject
| JsonArray
JSON 字段的使用案例
将数据存储为 JSON 而不是将其表示为相关模型的原因包括
- 您需要存储结构不一致的数据
- 您正在从另一个系统导入数据,并且不想将这些数据映射到 Prisma 模型
读取 Json 字段
您可以使用 Prisma.JsonArray 和 Prisma.JsonObject 实用程序类来处理 Json 字段的内容
const { PrismaClient, Prisma } = require('@prisma/client')
const user = await prisma.user.findFirst({
where: {
id: 9,
},
})
// Example extendedPetsData data:
// [{ name: 'Bob the dog' }, { name: 'Claudine the cat' }]
if (
user?.extendedPetsData &&
typeof user?.extendedPetsData === 'object' &&
Array.isArray(user?.extendedPetsData)
) {
const petsObject = user?.extendedPetsData as Prisma.JsonArray
const firstPet = petsObject[0]
}
另请参阅:高级示例:更新嵌套的 JSON 键值
写入 Json 字段
以下示例将 JSON 对象写入 extendedPetsData 字段
var json = [
{ name: 'Bob the dog' },
{ name: 'Claudine the cat' },
] as Prisma.JsonArray
const createUser = await prisma.user.create({
data: {
email: 'birgitte@prisma.io',
extendedPetsData: json,
},
})
注意:JavaScript 对象(例如,
{ extendedPetsData: "none"})会自动转换为 JSON。
另请参阅:高级示例:更新嵌套的 JSON 键值
筛选 Json 字段 (简单)
您可以筛选 Json 类型的行。
按精确字段值筛选
以下查询返回所有 extendedPetsData 值与 json 变量完全匹配的用户
var json = [{ name: 'Bob the dog' }, { name: 'Claudine the cat' }]
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
equals: json,
},
},
})
以下查询返回所有 extendedPetsData 值与 json 变量不完全匹配的用户
var json = [{ name: 'Bob the dog' }, { name: 'Claudine the cat' }]
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
not: json,
},
},
})
筛选 Json 字段 (高级)
您还可以按 Json 字段内的数据筛选行。我们称之为高级 Json 筛选。此功能仅受 PostgreSQL 和 MySQL 支持,并且 path 选项的语法不同。
PostgreSQL 不支持筛选数组中的对象键值。
高级 Json 筛选的可用性取决于您的 Prisma 版本
- v4.0.0 或更高版本:高级
Json筛选普遍可用。 - 从 v2.23.0 到 v4.0.0 之前:高级
Json筛选是预览功能。将previewFeatures = ["filterJson"]添加到您的 schema。了解更多。 - 在 v2.23.0 之前:您可以按精确的
Json字段值筛选,但不能使用本节中描述的其他功能。
path 语法取决于数据库
下面的筛选器使用 path 选项来选择 Json 值的特定部分进行筛选。此筛选的实现因连接器而异
例如,以下是一个有效的 MySQL path 值
$petFeatures.petName
以下是一个有效的 PostgreSQL path 值
["petFeatures", "petName"]
按对象属性筛选
您可以筛选 JSON 块中的特定属性。在以下示例中,extendedPetsData 的值是一个一维的、未嵌套的 JSON 对象
{
"petName": "Claudine",
"petType": "House cat"
}
以下查询返回所有 petName 值为 "Claudine" 的用户
- PostgreSQL
- MySQL
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['petName'],
equals: 'Claudine',
},
},
})
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$.petName',
equals: 'Claudine',
},
},
})
以下查询返回所有 petType 值包含 "cat" 的用户
- PostgreSQL
- MySQL
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['petType'],
string_contains: 'cat',
},
},
})
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$.petType',
string_contains: 'cat',
},
},
})
以下字符串筛选器可用
要使用不区分大小写的筛选器,您可以使用 mode 选项
- PostgreSQL
- MySQL
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['petType'],
string_contains: 'cat',
mode: 'insensitive'
},
},
})
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$.petType',
string_contains: 'cat',
mode: 'insensitive'
},
},
})
按嵌套对象属性筛选
您可以筛选嵌套的 JSON 属性。在以下示例中,extendedPetsData 的值是一个具有多个嵌套级别的 JSON 对象。
{
"pet1": {
"petName": "Claudine",
"petType": "House cat"
},
"pet2": {
"petName": "Sunny",
"petType": "Gerbil",
"features": {
"eyeColor": "Brown",
"furColor": "White and black"
}
}
}
以下查询返回所有 "pet2" → "petName" 为 "Sunny" 的用户
- PostgreSQL
- MySQL
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['pet2', 'petName'],
equals: 'Sunny',
},
},
})
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$.pet2.petName',
equals: 'Sunny',
},
},
})
以下查询返回所有用户,其中
"pet2"→"petName"为"Sunny""pet2"→"features"→"furColor"包含"black"
- PostgreSQL
- MySQL
const getUsers = await prisma.user.findMany({
where: {
AND: [
{
extendedPetsData: {
path: ['pet2', 'petName'],
equals: 'Sunny',
},
},
{
extendedPetsData: {
path: ['pet2', 'features', 'furColor'],
string_contains: 'black',
},
},
],
},
})
const getUsers = await prisma.user.findMany({
where: {
AND: [
{
extendedPetsData: {
path: '$.pet2.petName',
equals: 'Sunny',
},
},
{
extendedPetsData: {
path: '$.pet2.features.furColor',
string_contains: 'black',
},
},
],
},
})
筛选数组值
您可以筛选标量数组(字符串、整数)中特定值的存在。在以下示例中,extendedPetsData 的值是一个字符串数组
["Claudine", "Sunny"]
以下查询返回所有名为 "Claudine" 的宠物用户
- PostgreSQL
- MySQL
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
array_contains: ['Claudine'],
},
},
})
在 PostgreSQL 中,array_contains 的值必须是一个数组,而不是一个字符串,即使数组只包含一个值。
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
array_contains: 'Claudine',
},
},
})
以下数组筛选器可用
筛选嵌套数组值
您可以筛选标量数组(字符串、整数)中特定值的存在。在以下示例中,extendedPetsData 的值包括嵌套的名称标量数组
{
"cats": { "owned": ["Bob", "Sunny"], "fostering": ["Fido"] },
"dogs": { "owned": ["Ella"], "fostering": ["Prince", "Empress"] }
}
标量值数组
以下查询返回所有寄养名为 "Fido" 的猫的用户
- PostgreSQL
- MySQL
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['cats', 'fostering'],
array_contains: ['Fido'],
},
},
})
在 PostgreSQL 中,array_contains 的值必须是一个数组,而不是一个字符串,即使数组只包含一个值。
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$.cats.fostering',
array_contains: 'Fido',
},
},
})
以下查询返回所有寄养名为 "Fido" 和 "Bob" 的猫的用户
- PostgreSQL
- MySQL
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['cats', 'fostering'],
array_contains: ['Fido', 'Bob'],
},
},
})
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$.cats.fostering',
array_contains: ['Fido', 'Bob'],
},
},
})
JSON 对象数组
- PostgreSQL
- MySQL
const json = [{ status: 'expired', insuranceID: 92 }]
const checkJson = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['insurances'],
array_contains: json,
},
},
})
const json = { status: 'expired', insuranceID: 92 }
const checkJson = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$.insurances',
array_contains: json,
},
},
})
-
如果您使用 PostgreSQL,则必须传入一个对象数组进行匹配,即使该数组只包含一个对象
[{ status: 'expired', insuranceID: 92 }]
// PostgreSQL如果您使用 MySQL,则必须传入一个单个对象进行匹配
{ status: 'expired', insuranceID: 92 }
// MySQL -
如果您的筛选数组包含多个对象,PostgreSQL 将仅在所有对象都存在时返回结果,而不是在至少一个对象存在时。
-
您必须将
array_contains设置为 JSON 对象,而不是字符串。如果您使用字符串,Prisma Client 会转义引号,查询将不会返回结果。例如array_contains: '[{"status": "expired", "insuranceID": 92}]'被发送到数据库为
[{\"status\": \"expired\", \"insuranceID\": 92}]
按索引定位数组元素
您可以筛选特定位置的元素值。
{ "owned": ["Bob", "Sunny"], "fostering": ["Fido"] }
- PostgreSQL
- MySQL
const getUsers = await prisma.user.findMany({
where: {
comments: {
path: ['owned', '1'],
string_contains: 'Bob',
},
},
})
const getUsers = await prisma.user.findMany({
where: {
comments: {
path: '$.owned[1]',
string_contains: 'Bob',
},
},
})
筛选数组中的对象键值
根据您的提供程序,您可以筛选数组中对象的键值。
数组中对象键值的筛选仅受 MySQL 数据库连接器支持。但是,您仍然可以筛选整个 JSON 对象的存在。
在以下示例中,extendedPetsData 的值是一个对象数组,其中包含一个嵌套的 insurances 数组,其中包含两个对象
[
{
"petName": "Claudine",
"petType": "House cat",
"insurances": [
{ "insuranceID": 92, "status": "expired" },
{ "insuranceID": 12, "status": "active" }
]
},
{
"petName": "Sunny",
"petType": "Gerbil"
},
{
"petName": "Gerald",
"petType": "Corn snake"
},
{
"petName": "Nanna",
"petType": "Moose"
}
]
以下查询返回所有至少有一只宠物是驼鹿的用户
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$[*].petType',
array_contains: 'Moose',
},
},
})
$[*]是宠物对象的根数组petType匹配任何宠物对象中的petType键
以下查询返回所有至少有一只宠物保险已过期的用户
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$[*].insurances[*].status',
array_contains: 'expired',
},
},
})
$[*]是宠物对象的根数组insurances[*]匹配任何宠物对象内的任何insurances数组status匹配任何保险对象中的任何status键
高级示例:更新嵌套的 JSON 键值
以下示例假定 extendedPetsData 的值是以下某种变体
{
"petName": "Claudine",
"petType": "House cat",
"insurances": [
{ "insuranceID": 92, "status": "expired" },
{ "insuranceID": 12, "status": "active" }
]
}
以下示例
- 获取所有用户
- 将每个保险对象的
"status"更改为"expired" - 获取所有 ID 为
92且保险已过期的用户
- PostgreSQL
- MySQL
const userQueries: string | any[] = []
getUsers.forEach((user) => {
if (
user.extendedPetsData &&
typeof user.extendedPetsData === 'object' &&
!Array.isArray(user.extendedPetsData)
) {
const petsObject = user.extendedPetsData as Prisma.JsonObject
const i = petsObject['insurances']
if (i && typeof i === 'object' && Array.isArray(i)) {
const insurancesArray = i as Prisma.JsonArray
insurancesArray.forEach((i) => {
if (i && typeof i === 'object' && !Array.isArray(i)) {
const insuranceObject = i as Prisma.JsonObject
insuranceObject['status'] = 'expired'
}
})
const whereClause = Prisma.validator<Prisma.UserWhereInput>()({
id: user.id,
})
const dataClause = Prisma.validator<Prisma.UserUpdateInput>()({
extendedPetsData: petsObject,
})
userQueries.push(
prisma.user.update({
where: whereClause,
data: dataClause,
})
)
}
}
})
if (userQueries.length > 0) {
console.log(userQueries.length + ' queries to run!')
await prisma.$transaction(userQueries)
}
const json = [{ status: 'expired', insuranceID: 92 }]
const checkJson = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['insurances'],
array_contains: json,
},
},
})
console.log(checkJson.length)
const userQueries: string | any[] = []
getUsers.forEach((user) => {
if (
user.extendedPetsData &&
typeof user.extendedPetsData === 'object' &&
!Array.isArray(user.extendedPetsData)
) {
const petsObject = user.extendedPetsData as Prisma.JsonObject
const insuranceList = petsObject['insurances'] // is a Prisma.JsonArray
if (Array.isArray(insuranceList)) {
insuranceList.forEach((insuranceItem) => {
if (
insuranceItem &&
typeof insuranceItem === 'object' &&
!Array.isArray(insuranceItem)
) {
insuranceItem['status'] = 'expired' // is a Prisma.JsonObject
}
})
const whereClause = Prisma.validator<Prisma.UserWhereInput>()({
id: user.id,
})
const dataClause = Prisma.validator<Prisma.UserUpdateInput>()({
extendedPetsData: petsObject,
})
userQueries.push(
prisma.user.update({
where: whereClause,
data: dataClause,
})
)
}
}
})
if (userQueries.length > 0) {
console.log(userQueries.length + ' queries to run!')
await prisma.$transaction(userQueries)
}
const json = { status: 'expired', insuranceID: 92 }
const checkJson = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$.insurances',
array_contains: json,
},
},
})
console.log(checkJson.length)
使用 null 值
SQL 数据库中的 JSON 字段可能存在两种类型的 null 值。
- 数据库
NULL:数据库中的值为NULL。 - JSON
null:数据库中的值包含一个 JSON 值,其为null。
为了区分这些可能性,我们引入了三个您可以使用的null 枚举
JsonNull:表示 JSON 中的null值。DbNull:表示数据库中的NULL值。AnyNull:表示nullJSON 值和NULL数据库值。(仅在筛选时)
从 v4.0.0 开始,JsonNull、DbNull 和 AnyNull 是对象。在 v4.0.0 之前,它们是字符串。
- 当使用任何null 枚举进行筛选时,您不能使用简写并省略
equals运算符。 - 这些null 枚举不适用于 MongoDB,因为 MongoDB 中 JSON
null和数据库NULL之间没有区别。 - 这些null 枚举不适用于所有数据库中的
array_contains运算符,因为 JSON 数组中只能有 JSONnull。由于 JSON 数组中不能有数据库NULL,因此{ array_contains: null }不会产生歧义。
例如
model Log {
id Int @id
meta Json
}
以下是使用 AnyNull 的示例
import { Prisma } from '@prisma/client'
prisma.log.findMany({
where: {
data: {
meta: {
equals: Prisma.AnyNull,
},
},
},
})
插入 null 值
这也适用于 create、update 和 upsert。要将 null 值插入到 Json 字段中,您可以这样写
import { Prisma } from '@prisma/client'
prisma.log.create({
data: {
meta: Prisma.JsonNull,
},
})
要将数据库 NULL 插入到 Json 字段中,您可以这样写
import { Prisma } from '@prisma/client'
prisma.log.create({
data: {
meta: Prisma.DbNull,
},
})
按 null 值筛选
要按 JsonNull 或 DbNull 筛选,您可以这样写
import { Prisma } from '@prisma/client'
prisma.log.findMany({
where: {
meta: {
equals: Prisma.AnyNull,
},
},
})
这些null 枚举不适用于 MongoDB,因为 MongoDB 不区分 JSON null 和数据库 NULL。它们也不适用于所有数据库中的 array_contains 运算符,因为 JSON 数组中只能有 JSON null。由于 JSON 数组中不能有数据库 NULL,因此 { array_contains: null } 不会产生歧义。
类型化 Json 字段
Prisma 的 Json 字段默认是无类型的。要添加强类型,您可以使用外部包 prisma-json-types-generator。
-
首先,安装包并将生成器添加到您的
schema.prismanpm install -D prisma-json-types-generatorschema.prismagenerator client {
provider = "prisma-client"
output = "./generated"
}
generator json {
provider = "prisma-json-types-generator"
} -
接下来,使用 AST 注释将字段链接到 TypeScript 类型。
schema.prismamodel Log {
id Int @id
/// [LogMetaType]
meta Json
} -
然后,在类型声明文件(例如,
types.ts)中定义LogMetaType,该文件包含在您的tsconfig.json中。types.tsdeclare global {
namespace PrismaJson {
type LogMetaType = { timestamp: number; host: string };
}
}
// This file must be a module.
export {};
现在,Log.meta 将被强类型化为 { timestamp: number; host: string }。
类型化 String 字段和高级功能
您也可以将这些技术应用于 String 字段。这对于在数据库不支持枚举类型时直接在您的 schema 中创建基于字符串的枚举特别有用。
model Post {
id Int @id
/// !['draft' | 'published']
status String
/// [LogMetaType]
meta Json[]
}
这将导致 post.status 被强类型化为 'draft' | 'published',post.meta 被强类型化为 LogMetaType[]。
有关配置、monorepo 设置和其他高级功能的完整指南,请参阅 官方 prisma-json-types-generator 文档。
Json 常见问题
您可以选择 JSON 键/值的子集返回吗?
否 - 尚不可能选择要返回的 JSON 元素。Prisma Client 返回整个 JSON 对象。
您可以根据特定键的存在进行筛选吗?
否 - 尚不可能根据特定键的存在进行筛选。
是否支持不区分大小写的筛选?
否 - 尚不支持不区分大小写的筛选。
您可以对 JSON 值中的对象属性进行排序吗?
否,目前不支持对 JSON 值中的对象属性进行排序(按属性排序)。
如何为 JSON 字段设置默认值?
当您想为 Json 类型设置 @default 值时,您需要将其用双引号括在 @default 属性中(并可能使用反斜杠转义任何“内部”双引号),例如
model User {
id Int @id @default(autoincrement())
json1 Json @default("[]")
json2 Json @default("{ \"hello\": \"world\" }")
}