Skip to main content

Prisma JSON Casts — почему они допустимы

Проблема

В кодовой базе ~55 кастов вида:

// JSON READ: Prisma.JsonValue → typed
const rewards = season.rewards as unknown as SeasonRewards;

// JSON WRITE: typed → Prisma.InputJsonValue
data: { rewards: preparedRewards as unknown as Prisma.InputJsonValue }

Почему эти касты НЕ являются code debt

Корневая причина

Prisma не поддерживает typed JSON columns. JSON-колонки всегда типизированы как JsonValue (чтение) и InputJsonValue (запись). Это известное ограничение Prisma.

Почему Zod validation при чтении — YAGNI

Все JSON-колонки в проекте записываются нашим же кодом через typed interfaces:

JSON-колонкаКто пишетИсточник данных
Season.rewardsadmin-season-rewards.controller.tsValidated admin body
Season.referralRewardsadmin-season-rewards.controller.tsValidated admin body
Season.boostLeaderboardRewardsadmin-season-rewards.controller.tsValidated admin body
PromoCode.rewardspromo-code.repository.tsCreatePromoCodeData (typed)
Quiz.optionsquiz.service.tsvalidateOptions() result
Quiz.correctAnswerquiz.service.tsValidated input
SteamTrade.items*Webhook handlerSanitized webhook payload
CraftHistory.materialsUsedcraft.service.tsComputed in transaction
SpinResult.rewardSnapshotuser-spin.service.tsConstructed in code
CaseOpening.rewardSnapshotcase-opening.service.tsConstructed in code
UserSettings.dismissedHintssettings.service.tsSimple key-value map
ConsolationAward.rewardsAdmin/systemTyped config

Данные не могут "сломаться" при чтении, потому что:

  1. Мы сами их записали через typed код
  2. Никто не пишет напрямую в БД мимо приложения
  3. Миграции не меняют JSON-структуру (только Prisma schema, не содержимое)

Когда Zod validation НУЖЕН

Zod/runtime validation оправдан только если:

  • JSON пишется внешней системой (не нашим кодом)
  • Есть миграция, меняющая структуру JSON (старые записи vs новые)
  • JSON доступен для прямого редактирования пользователем

В нашем проекте ни одно из этих условий не выполняется.

Категории допустимых кастов

КатегорияКол-воПричинаФиксить?
JSON WRITE (→ Prisma.InputJsonValue)~24Ограничение PrismaНет
JSON READ (JsonValue → Type)~28Ограничение PrismaНет
Multipart (part as unknown as { value })3Ограничение @fastify/multipartНет
UTM validator (Record<string, unknown>)1TypeScript indexed writesКосметика

Что было сделано (Plans 00-04)

Plans 00-04 убрали все реальные type safety проблемы:

ПланЧто убралКастов
Plan 00(fastify as any).prisma → typed decorators~43
Plan 01-02request.body as Type → route generics~110
Plan 03Mapper/include type mismatches → as const satisfies + GetPayload5
Plan 04Service return type mismatches → explicit return types8
Итого~166

Оставшиеся ~56 кастов — граница типизации ORM, не код-долг.

Правило для будущих рефакторингов

Не трогать JSON касты

НЕ создавать readJsonField<T>() / writeJsonField<T>() хелперы.

Они перемещают каст внутрь функции, но не добавляют type safety:

// Внутри readJsonField — тот же каст:
return value as unknown as T; // TypeScript по-прежнему "верит на слово"

Это code organization (DRY), но не type safety. Если нет бизнес-требования — не тратить время.

Единственный реальный фикс

Если Prisma когда-нибудь добавит typed JSON fields:

// Гипотетический синтаксис (не существует в Prisma)
model Season {
rewards Json @type(SeasonRewards)
}

Тогда все касты исчезнут автоматически. До тех пор — касты допустимы.