AZ.dev

typescript-eslint --fix can break Prisma JSON column writes

| prisma: 5+ typescript-eslint: 8.59+

@typescript-eslint/no-unnecessary-type-assertion (tightened in 8.59) flags as unknown as object casts at Prisma JSON write sites as “unnecessary.” They aren’t. Running --fix removes them and breaks the build in a delayed, surprising way.

The Pattern

Before --fix:

await db.learningConversation.create({
  data: {
    messages: (input.messages ?? []) as unknown as object,
    context: (input.context ?? {}) as object,
  },
});

After --fix (lint says: “unnecessary since receiver accepts the original type”):

await db.learningConversation.create({
  data: {
    messages: input.messages ?? [],
    context: input.context ?? {},
  },
});

tsc then fails:

Type 'ChatMessage[]' is not assignable to type 'JsonNullClass | InputJsonValue | undefined'.
  Type 'ChatMessage[]' is not assignable to type 'InputJsonObject'.
    Index signature for type 'string' is missing in type 'ChatMessage[]'.

Why It Happens

Prisma.InputJsonValue is string | number | boolean | InputJsonObject | InputJsonArray | { toJSON(): unknown } | { [key: string]: InputJsonValue }. The string-indexed signature makes the lint analyzer conclude that any object is structurally assignable, so the cast looks redundant. TypeScript’s nominal check disagrees: a ChatMessage[] (typed elements, not InputJsonValue) does not satisfy InputJsonArray. The lint rule and the type checker reach different conclusions, and the cast is doing real work that the rule cannot see.

The Fix

Funnel writes through one boundary helper instead of scattering casts:

// infrastructure/prisma-json.ts
import type { Prisma } from "../generated/prisma/client.js";

export const toJsonValue = (value: unknown): Prisma.InputJsonValue =>
  value as Prisma.InputJsonValue;
import { toJsonValue } from "../infrastructure/prisma-json.js";

await db.learningConversation.create({
  data: {
    messages: toJsonValue(input.messages ?? []),
    context: toJsonValue(input.context ?? {}),
  },
});

A function call isn’t a cast, so the lint rule leaves it alone. The unsafe-but-necessary conversion happens in one named place at the persistence boundary, and the helper is the single point of change if Prisma’s input type ever shifts.

Gotchas

eslint typescript-eslint prisma type-assertion lint-fix upgrade

Was this helpful?