跳到主要内容

使用 Json 字段

使用 Json Prisma ORM 字段类型来读取、写入和对底层数据库中的 JSON 类型执行基本过滤。在以下示例中,User 模型有一个名为 extendedPetsData 的可选 Json 字段

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 字段支持一些额外的类型,例如 stringboolean。这些额外的类型旨在匹配 JSON.parse() 支持的类型

export type JsonValue =
| string
| number
| boolean
| null
| JsonObject
| JsonArray

JSON 字段的使用场景

将数据存储为 JSON 而非表示为关联模型的原因包括

  • 你需要存储没有一致结构的数据
  • 你正在从另一个系统导入数据,并且不想将这些数据映射到 Prisma 模型

读取 Json 字段

你可以使用 Prisma.JsonArrayPrisma.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 = {
extendedPetsData: [{ name: 'Bob the dog' }, { name: 'Claudine the cat' }],
}

const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
not: json,
},
},
})

过滤 Json 字段(高级)

你还可以通过 Json 字段内部的数据来过滤行。我们称之为高级 Json 过滤。此功能仅由 PostgreSQLMySQL 支持,且 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" 的用户

const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['petName'],
equals: 'Claudine',
},
},
})

以下查询返回所有 petType包含 "cat" 的用户

const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['petType'],
string_contains: 'cat',
},
},
})

以下字符串过滤器可用

要将这些过滤器与不区分大小写一起使用,你可以使用 mode 选项

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" 的用户

const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['pet2', 'petName'],
equals: 'Sunny',
},
},
})

以下查询返回所有满足以下条件的用户

  • "pet2""petName""Sunny"
  • "pet2""features""furColor" 包含 "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" 的用户

const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
array_contains: ['Claudine'],
},
},
})
信息

注意:在 PostgreSQL 中,array_contains 的值必须是一个数组而不是字符串,即使该数组只包含一个值。

以下数组过滤器可用

按嵌套数组值过滤

你可以过滤标量数组(字符串、整数)中特定值的存在。在以下示例中,extendedPetsData 的值包括嵌套的标量名称数组

{
"cats": { "owned": ["Bob", "Sunny"], "fostering": ["Fido"] },
"dogs": { "owned": ["Ella"], "fostering": ["Prince", "Empress"] }
}

标量值数组

以下查询返回所有寄养名为 "Fido" 的猫的用户

const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['cats', 'fostering'],
array_contains: ['Fido'],
},
},
})
信息

注意:在 PostgreSQL 中,array_contains 的值必须是一个数组而不是字符串,即使该数组只包含一个值。

以下查询返回所有寄养名为 "Fido" "Bob" 的猫的用户

const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: ['cats', 'fostering'],
array_contains: ['Fido', 'Bob'],
},
},
})

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"] }
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" }
]
}

以下示例

  1. 获取所有用户
  2. 将每个保险对象的 "status" 更改为 "expired"
  3. 获取所有 ID 为 92 且保险已过期的用户
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)

使用 null

SQL 数据库中 JSON 字段可能存在两种类型的 null 值。

  • 数据库 NULL:数据库中的值为 NULL
  • JSON null:数据库中的值包含一个 JSON 值为 null

为了区分这些可能性,我们引入了三个可用的空枚举

  • JsonNull:表示 JSON 中的 null 值。
  • DbNull:表示数据库中的 NULL 值。
  • AnyNull:表示 JSON null 值和数据库 NULL 值。(仅在过滤时)
信息

从 v4.0.0 起,JsonNullDbNullAnyNull 是对象。在 v4.0.0 之前,它们是字符串。

信息
  • 使用任何空枚举进行过滤时,不能使用简写形式并省略 equals 操作符。
  • 这些空枚举不适用于 MongoDB,因为在 MongoDB 中 JSON null 和数据库 NULL 之间没有区别。
  • 这些空枚举不适用于所有数据库中的 array_contains 操作符,因为 JSON 数组中只能有 JSON null。由于 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

这也适用于 createupdateupsert。要将 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 值过滤

要按 JsonNullDbNull 过滤,你可以这样写

import { Prisma } from '@prisma/client'

prisma.log.findMany({
where: {
meta: {
equals: Prisma.AnyNull,
},
},
})
信息

这些空枚举不适用于 MongoDB,因为 MongoDB 不区分 JSON null 和数据库 NULL。它们也不适用于所有数据库中的 array_contains 操作符,因为 JSON 数组中只能有 JSON null。由于 JSON 数组中不能有数据库 NULL,因此 { array_contains: null } 没有歧义。

类型化 Json

默认情况下,Prisma 模型中的 Json 字段没有类型。要实现这些字段内部的强类型,你需要使用像 prisma-json-types-generator 这样的外部包来完成此操作。

使用 prisma-json-types-generator

首先,根据包的说明安装和配置 prisma-json-types-generator

然后,假设你有一个如下所示的模型

model Log {
id Int @id
meta Json
}

你可以使用抽象语法树注释来更新和类型化它

schema.prisma
model Log {
id Int @id

/// [LogMetaType]
meta Json
}

然后,确保你在 tsconfig.json 中包含的类型声明文件中定义了上述类型

types.ts
declare global {
namespace PrismaJson {
type LogMetaType = { timestamp: number; host: string }
}
}

现在,在使用 Log.meta 时,它将是强类型化的!

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\" }")
}
© . All rights reserved.