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

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フィールドは、stringbooleanなどのいくつかの追加タイプをサポートしています。これらの追加タイプは、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キーの値を更新する

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フィールドでフィルタリング (単純)

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構文

以下のフィルターは、Json値の特定の箇所をフィルタリングするためにpathオプションを使用します。このフィルタリングの実装は、コネクタによって異なります。

例えば、以下は有効なMySQLのpath値です。

$petFeatures.petName

以下は有効なPostgreSQLのpath値です。

["petFeatures", "petName"]

オブジェクトプロパティでフィルタリング

JSONブロック内の特定のプロパティでフィルタリングできます。以下の例では、extendedPetsDataの値は1次元のネストされていない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配列を持つオブジェクトの配列であり、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: データベース内の値にJSON値としてnullが含まれている。

これらの可能性を区別するために、以下の3つの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にも適用されます。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
}

抽象構文ツリーコメントを使用して、それを更新し型付けできます。

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値内のオブジェクトプロパティのソート(プロパティによる並べ替え)は現在サポートされていません。

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

Json型の@default値を設定する場合、@default属性内で二重引用符で囲む必要があります(また、内部の二重引用符はバックスラッシュでエスケープする必要がある場合があります)。例えば、

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