使用 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 欄位支援一些額外的型別,例如 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 = {
extendedPetsData: [{ 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 聯結器使用 MySQL 的 JSON 路徑實現
- PostgreSQL 聯結器使用版本 12 及更早版本中支援的自定義 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。
為了區分這些可能性,我們引入了三個可用的空列舉
JsonNull:表示 JSON 中的null值。DbNull:表示資料庫中的NULL值。AnyNull:表示 JSONnull值和資料庫NULL值。(僅在過濾時)
從 v4.0.0 起,JsonNull、DbNull 和 AnyNull 是物件。在 v4.0.0 之前,它們是字串。
- 使用任何空列舉進行過濾時,不能使用簡寫形式並省略
equals運算子。 - 這些空列舉不適用於 MongoDB,因為在 MongoDB 中 JSON
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,
},
},
})
這些空列舉不適用於 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
}
你可以使用抽象語法樹註釋來更新和型別化它
model Log {
id Int @id
/// [LogMetaType]
meta Json
}
然後,確保你在 tsconfig.json 中包含的型別宣告檔案中定義了上述型別
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\" }")
}