TypeScript中jackson-js多态类型拆分文件后反序列化Super表达式错误问题咨询
I've run into this exact issue with jackson-js when splitting polymorphic classes into separate files, so let's break down what's happening and how to fix it.
The Problem Recap
When your Animal base class and Dog subclass are in the same file, jackson-js handles serialization/deserialization perfectly. But once you split them into separate files, calling objectMapper.parse() throws the error: "Super expression must either be null or a function". Even using import type { Dog } from "./Dog" doesn't fix it.
Why This Happens
The root cause is circular module references:
- The
Animalclass's@JsonSubTypesdecorator needs to referenceDog - The
Dogclass inherits fromAnimal
In ES module loading, this circular dependency means one of the classes won't be fully initialized when jackson-js tries to access it. When the decorator's () => Dog runs, it might get undefined instead of the actual class constructor. Using import type makes this worse because type-only imports are stripped at runtime—so Dog doesn't exist at all when jackson-js needs it.
Fixes That Work
Here are two reliable solutions to resolve this:
Solution 1: Use Dynamic require to Break the Cycle
Instead of importing Dog directly in Animal.ts, use Node.js's require() inside the @JsonSubTypes class factory function. This delays loading Dog until it's actually needed, avoiding the circular dependency during module initialization.
Update Animal.ts:
import { JsonClassType, JsonProperty, JsonTypeInfo, JsonTypeInfoId, JsonTypeInfoAs, JsonSubTypes } from "jackson-js"; @JsonTypeInfo({ use: JsonTypeInfoId.NAME, include: JsonTypeInfoAs.PROPERTY, property: "type" }) @JsonSubTypes({ types: [ { class: () => require('./Dog').Dog, // Dynamically load Dog when needed name: "dog" } ] }) export class Animal { @JsonProperty() @JsonClassType({ type: () => [String] }) name: string; @JsonProperty() @JsonClassType({ type: () => [String] }) type: string; constructor(type: string, name: string) { this.type = type; this.name = name; } say() { console.log("Ah!"); } }
Keep Dog.ts unchanged:
import { JsonTypeName } from "jackson-js"; import { Animal } from "./Animal"; @JsonTypeName({ value: "dog" }) export class Dog extends Animal { constructor(name: string) { super("dog", name); } say() { console.log("Wang!"); } }
Your test code stays the same—it'll work without any changes.
Solution 2: Manually Register Subclasses with ObjectMapper
Skip the @JsonSubTypes decorator on the base class, and instead register your subclasses directly with the ObjectMapper instance. This gives you explicit control over class loading order, eliminating the circular dependency issue.
- Update
Animal.tsto remove@JsonSubTypes:
import { JsonClassType, JsonProperty, JsonTypeInfo, JsonTypeInfoId, JsonTypeInfoAs } from "jackson-js"; @JsonTypeInfo({ use: JsonTypeInfoId.NAME, include: JsonTypeInfoAs.PROPERTY, property: "type" }) export class Animal { @JsonProperty() @JsonClassType({ type: () => [String] }) name: string; @JsonProperty() @JsonClassType({ type: () => [String] }) type: string; constructor(type: string, name: string) { this.type = type; this.name = name; } say() { console.log("Ah!"); } }
Keep
Dog.tsunchanged.Modify your test code to register the subclass:
import { ObjectMapper } from "jackson-js"; import { Animal } from "./Animal"; import { Dog } from "./Dog"; const dog = new Dog("Super"); const objectMapper = new ObjectMapper(); // Register Dog with the ObjectMapper objectMapper.registerSubtypes([{ class: Dog, name: "dog" }]); const jsonString = objectMapper.stringify<Animal>(dog); console.log(jsonString); // {"name":"Super","type":"dog"} const animal = objectMapper.parse<Animal>(jsonString, { mainCreator: () => [Animal] }); animal.say(); // Wang!
Verify the Fix
Either solution will resolve the error. When you run your test code, you'll see the expected JSON output and "Wang!" logged to the console—confirming that polymorphic deserialization works correctly even with split files.
内容的提问来源于stack exchange,提问作者yejianfengblue

