Jsonフィールドの操作
基盤となるデータベースのJSON型を読み書きしたり、基本的なフィルタリングを実行したりするには、Json
Prisma ORMフィールド型を使用します。以下の例では、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
フィールドの読み込み
Json
フィールドの内容を操作するには、Prisma.JsonArray
およびPrisma.JsonObject
ユーティリティクラスを使用できます。
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
フィールドへの書き込み
以下の例は、extendedPetsData
フィールドにJSONオブジェクトを書き込みます。
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
型の行をフィルタリングできます。
フィールドの正確な値でフィルタリング
以下のクエリは、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
フィルタリングは一般提供 (GA)されています。 - v2.23.0以降、v4.0.0より前: 高度な
Json
フィルタリングはプレビュー機能です。スキーマにpreviewFeatures = ["filterJson"]
を追加してください。詳細はこちら。 - v2.23.0より前: 正確な
Json
フィールド値でフィルタリングできますが、このセクションで説明されている他の機能は使用できません。
データベースによるpath
構文
以下のフィルターは、Json
値の特定の箇所をフィルタリングするためにpath
オプションを使用します。このフィルタリングの実装は、コネクタによって異なります。
- MySQLコネクタは、MySQLのJSONパス実装を使用します。
- PostgreSQLコネクタは、バージョン12以前でサポートされているカスタムJSON関数と演算子を使用します。
例えば、以下は有効なMySQLのpath
値です。
$petFeatures.petName
以下は有効なPostgreSQLのpath
値です。
["petFeatures", "petName"]
オブジェクトプロパティでフィルタリング
JSONブロック内の特定のプロパティでフィルタリングできます。以下の例では、extendedPetsData
の値は1次元のネストされていない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を使用している場合、一致させるオブジェクトの配列を渡す必要があります。たとえその配列に1つのオブジェクトしか含まれていない場合でもです。
[{ status: 'expired', insuranceID: 92 }]
// PostgreSQLMySQLを使用している場合、一致させる単一のオブジェクトを渡す必要があります。
{ status: 'expired', insuranceID: 92 }
// MySQL -
フィルター配列に複数のオブジェクトが含まれている場合、PostgreSQLはすべてのオブジェクトが存在する場合にのみ結果を返し、少なくとも1つのオブジェクトが存在する場合ではありません。
-
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
配列を持つオブジェクトの配列であり、insurances
配列には2つのオブジェクトが含まれています。
[
{
"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"
}
]
以下のクエリは、少なくとも1匹のペットがヘラジカであるすべてのユーザーを返します。
const getUsers = await prisma.user.findMany({
where: {
extendedPetsData: {
path: '$[*].petType',
array_contains: 'Moose',
},
},
})
$[*]
はペットオブジェクトのルート配列です。petType
は、任意のペットオブジェクト内のpetType
キーと一致します。
以下のクエリは、少なくとも1匹のペットが期限切れの保険に加入しているすべてのユーザーを返します。
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
フィールドには、2種類のnull
値が可能です。
- データベースの
NULL
: データベース内の値がNULL
である。 - JSONの
null
: データベース内の値にJSON値としてnull
が含まれている。
これらの可能性を区別するために、以下の3つのnull列挙型を導入しました。
JsonNull
: JSONにおけるnull
値を表します。DbNull
: データベースにおけるNULL
値を表します。AnyNull
:null
JSON値とNULL
データベース値の両方を表します。(フィルタリング時のみ)
v4.0.0以降、JsonNull
、DbNull
、AnyNull
はオブジェクトになりました。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
値の挿入
これはcreate
、update
、upsert
にも適用されます。Json
フィールドにnull
値を挿入するには、次のように記述します。
import { Prisma } from '@prisma/client'
prisma.log.create({
data: {
meta: Prisma.JsonNull,
},
})
そして、Json
フィールドにデータベースのNULL
を挿入するには、次のように記述します。
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のような外部パッケージを使用する必要があります。
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
FAQ
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\" }")
}