メインコンテンツにスキップ

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.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 のバージョンによって異なります。

データベースに依存する 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 を使用している場合は、配列にオブジェクトが 1 つしか含まれていない場合でも、一致させるオブジェクトの配列を渡す必要があります。

    [{ status: 'expired', insuranceID: 92 }]
    // PostgreSQL

    MySQL を使用している場合は、一致させる単一のオブジェクトを渡す必要があります。

    { 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"] }
const getUsers = await prisma.user.findMany({
where: {
comments: {
path: ['owned', '1'],
string_contains: 'Bob',
},
},
})

配列内のオブジェクトキー値でのフィルタリング

プロバイダーによっては、配列内のオブジェクトのキー値でフィルタリングできます。

警告

配列内のオブジェクトキー値でのフィルタリングは、MySQL データベースコネクタのみでサポートされています。ただし、JSON オブジェクト全体の存在をフィルタリングすることはできます。

次の例では、extendedPetsData の値は、ネストされた 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" }
]
}

次の例:

  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 フィールドには、2 種類の null 値が考えられます。

  • データベース NULL: データベースの値は NULL です。
  • JSON null: データベースの値には null である JSON 値が含まれています。

これらの可能性を区別するために、使用できる 3 つのnull 列挙型を導入しました。

  • JsonNull: JSON の null 値を表します。
  • DbNull: データベースの NULL 値を表します。
  • AnyNull: null JSON 値と NULL データベース値の両方を表します (フィルタリング時のみ)。
情報

v4.0.0 以降、JsonNullDbNull、および AnyNull はオブジェクトです。v4.0.0 より前は、文字列でした。

情報
  • null 列挙型のいずれかを使用してフィルタリングする場合、短縮形を使用したり、equals 演算子を省略したりすることはできません。
  • これらのnull 列挙型は、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 値の挿入

これは、createupdate、および upsert にも適用されます。null 値を Json フィールドに挿入するには、次のように記述します。

import { Prisma } from '@prisma/client'

prisma.log.create({
data: {
meta: Prisma.JsonNull,
},
})

データベース NULLJson フィールドに挿入するには、次のように記述します。

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

デフォルトでは、Json フィールドは Prisma モデルで型指定されていません。これらのフィールド内で強力な型指定を実現するには、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 に関する FAQ

JSON キー/値のサブセットを選択して返すことはできますか?

いいえ - まだ 返す JSON 要素を選択 することはできません。Prisma Client は JSON オブジェクト全体を返します。

特定のキーの存在でフィルタリングできますか?

いいえ - 特定のキーの存在でフィルタリングすることはまだできません。

大文字と小文字を区別しないフィルタリングはサポートされていますか?

いいえ - 大文字と小文字を区別しないフィルタリング はまだサポートされていません。

JSON 値内のオブジェクトプロパティをソートできますか?

いいえ、JSON 値内のオブジェクトプロパティのソート (order-by-prop) は現在サポートされていません。

JSON フィールドのデフォルト値を設定する方法は?

Json 型の @default 値を設定する場合は、@default 属性内で二重引用符で囲む必要があります (および、必要に応じて「内側」の二重引用符をバックスラッシュを使用してエスケープします)。例:

model User {
id Int @id @default(autoincrement())
json1 Json @default("[]")
json2 Json @default("{ \"hello\": \"world\" }")
}