跳至主要内容

使用 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 字段支持一些额外的类型,例如 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 = [{ 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 版本

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

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

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

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

信息
  • 当使用任何null 枚举进行筛选时,您不能使用简写并省略 equals 运算符。
  • 这些null 枚举不适用于 MongoDB,因为 MongoDB 中 JSON null 和数据库 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,
},
},
})
信息

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

类型化 Json 字段

Prisma 的 Json 字段默认是无类型的。要添加强类型,您可以使用外部包 prisma-json-types-generator

  1. 首先,安装包并将生成器添加到您的 schema.prisma

    npm install -D prisma-json-types-generator
    schema.prisma
    generator client {
    provider = "prisma-client"
    output = "./generated"
    }

    generator json {
    provider = "prisma-json-types-generator"
    }
  2. 接下来,使用 AST 注释将字段链接到 TypeScript 类型。

    schema.prisma
    model Log {
    id Int @id

    /// [LogMetaType]
    meta Json
    }
  3. 然后,在类型声明文件(例如,types.ts)中定义 LogMetaType,该文件包含在您的 tsconfig.json 中。

    types.ts
    declare 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\" }")
}
© . This site is unofficial and not affiliated with Prisma Data, Inc.