@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
- Always run
tscaftereslint --fix. The lint pass goes clean; the build doesn’t. Anywhere--fixtouched a Prisma JSON write is a candidate. - Inline
as Prisma.InputJsonValuetriggers the same rule. The fix has to be a function boundary, not a different cast. - An inline
// eslint-disable-next-lineworks but scatters the suppression across every JSON write site, and turns invisible the moment you add another one. - Patch releases of typescript-eslint routinely tighten rules without bumping SemVer — “lint clean” is only stable within a single minor version. Pin the version or run
--fixin a dedicated commit so the cascade is easy to revert.