diff --git a/src/structs/types.ts b/src/structs/types.ts index 3eeb2bf4..92dcdf61 100644 --- a/src/structs/types.ts +++ b/src/structs/types.ts @@ -418,7 +418,7 @@ export function set(Element?: Struct): any { schema: null, *entries(value) { if (Element && value instanceof Set) { - for (const v of value) { + for (const v of [...value]) { yield [v as string, v, Element] } } diff --git a/src/utils.ts b/src/utils.ts index 5c05d1e1..0ddceacf 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -153,6 +153,11 @@ export function* run( yield [failure, undefined] } + // `Set.add()` can only insert members, never replace one positionally, so a + // coerced element would accumulate alongside its original. Clear the set once + // on the first coerced write-back so only coerced members remain. + let clearedSet = false + for (let [k, v, s] of struct.entries(value, ctx)) { const ts = run(v, s as Struct, { path: k === undefined ? path : [...path, k], @@ -174,6 +179,10 @@ export function* run( } else if (value instanceof Map) { value.set(k, v) } else if (value instanceof Set) { + if (!clearedSet) { + value.clear() + clearedSet = true + } value.add(v) } else if (isObject(value)) { if (v !== undefined || k in value) value[k] = v diff --git a/test/validation/set/coerce-element.ts b/test/validation/set/coerce-element.ts new file mode 100644 index 00000000..0e86bba5 --- /dev/null +++ b/test/validation/set/coerce-element.ts @@ -0,0 +1,11 @@ +import { coerce, number, set, string } from '../../../src' + +const toNumber = coerce(number(), string(), (v) => parseFloat(v)) + +export const Struct = set(toNumber) + +export const data = new Set(['1', '2']) + +export const output = new Set([1, 2]) + +export const create = true