diff --git a/src/json.test.ts b/src/json.test.ts index c4e620a58..96c16bec8 100644 --- a/src/json.test.ts +++ b/src/json.test.ts @@ -239,6 +239,18 @@ describe('json', () => { 'Expected a value of type `JSON`, but received: `undefined`', ); }); + + it('returns a readable error message for a nested JsonStruct', () => { + const struct = object({ + value: JsonStruct, + }); + + const [error] = validate({ value: undefined }, struct); + assert(error !== undefined); + expect(error.message).toBe( + 'At path: value -- Expected a value of type `JSON`, but received: `undefined`', + ); + }); }); describe('getSafeJson', () => { diff --git a/src/json.ts b/src/json.ts index 159ce5e37..0182a332b 100644 --- a/src/json.ts +++ b/src/json.ts @@ -16,6 +16,7 @@ import { union, unknown, Struct, + refine, } from '@metamask/superstruct'; import type { Context, @@ -215,18 +216,20 @@ export const UnsafeJsonStruct: Struct = define('JSON', (json) => * This struct sanitizes the value before validating it, so that it is safe to * use with untrusted input. */ -export const JsonStruct = coerce(UnsafeJsonStruct, any(), (value) => { - assertStruct(value, UnsafeJsonStruct); - return JSON.parse( - JSON.stringify(value, (propKey, propValue) => { - // Strip __proto__ and constructor properties to prevent prototype pollution. - if (propKey === '__proto__' || propKey === 'constructor') { - return undefined; - } - return propValue; - }), - ); -}); +export const JsonStruct = coerce( + UnsafeJsonStruct, + refine(any(), 'JSON', (value) => is(value, UnsafeJsonStruct)), + (value) => + JSON.parse( + JSON.stringify(value, (propKey, propValue) => { + // Strip __proto__ and constructor properties to prevent prototype pollution. + if (propKey === '__proto__' || propKey === 'constructor') { + return undefined; + } + return propValue; + }), + ), +); /** * Check if the given value is a valid {@link Json} value, i.e., a value that is