angular/angular-cli · error · Error

Argument ${isNonPrimitive(arg) ? arg.toString() : arg} is JS

Error message

Argument ${isNonPrimitive(arg) ? arg.toString() : arg} is JSON serializable.

What it means

`memoize` builds its cache key with `JSON.stringify(args)`, so every argument must be JSON-serializable. Before calling the wrapped function it checks each argument with `isJSONSerializable` and throws this error for any non-serializable argument. (The message text reads 'is JSON serializable' but the intent is that the argument must be.)

Source

Thrown at packages/angular/cli/src/utilities/memoize.ts:29

 *
 * **Note**: Be cautious where and how to use this decorator as the size of the cache will grow unbounded.
 *
 * @see https://en.wikipedia.org/wiki/Memoization
 */
export function memoize<This, Args extends unknown[], Return>(
  target: (this: This, ...args: Args) => Return,
  context: ClassMemberDecoratorContext,
) {
  if (context.kind !== 'method' && context.kind !== 'getter') {
    throw new Error('Memoize decorator can only be used on methods or get accessors.');
  }

  const cache = new Map<string, Return>();

  return function (this: This, ...args: Args): Return {
    for (const arg of args) {
      if (!isJSONSerializable(arg)) {
        throw new Error(
          `Argument ${isNonPrimitive(arg) ? arg.toString() : arg} is JSON serializable.`,
        );
      }
    }

    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key) as Return;
    }

    const result = target.apply(this, args);
    cache.set(key, result);

    return result;
  };
}

/** Method to check if value is a non primitive. */

View on GitHub (pinned to bb72145f9a)

Solutions

  1. Pass only JSON-serializable arguments (strings, numbers, booleans, plain objects/arrays) to memoized methods.
  2. Refactor to pass a stable primitive key (e.g. an id or path) instead of an object, and look up the object inside the method.
  3. Drop the `@memoize` decorator and implement an explicit cache keyed appropriately for complex arguments.
  4. Sanitize arguments before the call (strip circular refs/functions) if they are only used as keys.

Example fix

// before
@memoize loadUser(user: User) {...}
service.loadUser(someUserInstance); // throws
// after
@memoize loadUser(userId: string) {...}
service.loadUser(someUserInstance.id);
Defensive patterns

Strategy: validation

Validate before calling

function isJsonSerializable(v: unknown): boolean {
  try { JSON.stringify(v); return true; } catch { return false; }
}
if (!isJsonSerializable(arg)) throw new TypeError('Argument must be JSON-serializable for memoized methods');

Type guard

function isJsonSerializableArg(v: unknown): v is string | number | boolean | null | JsonValue {
  try { JSON.stringify(v); return true; } catch { return false; }
}

Try / catch

try {
  memoizedService.load(complexObject);
} catch (e) {
  if (e instanceof Error && e.message.includes('is JSON serializable')) {
    // retry with a primitive key instead
    memoizedService.load(complexObject.id);
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling a `@memoize`-decorated method/getter with arguments that fail `isJSONSerializable` — objects with circular references, class instances with functions/Symbols, `undefined`, functions, DOM nodes, Buffers, etc.

Common situations: Passing `this`-bound objects, HttpParams/Request objects, or class instances as arguments to memoized methods; passing `undefined` or callback functions; memoizing methods that take complex runtime objects instead of primitive IDs.

Related errors


AI-assisted analysis of angular/angular-cli@bb72145f9a (2026-08-30). Data as JSON: /api/errors/bd278b22d7e1b953. Report an issue: GitHub.