Skip to content

Commit 828fa1c

Browse files
authored
Enable support for custom reducer/reviver for "function" values (#123)
1 parent 6f4eb8b commit 828fa1c

File tree

4 files changed

+90
-9
lines changed

4 files changed

+90
-9
lines changed

.changeset/moody-bees-jog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"devalue": minor
3+
---
4+
5+
Enable support for custom reducer/reviver for "function" values

src/stringify.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@ export function stringify(value, reducers) {
4444

4545
/** @param {any} thing */
4646
function flatten(thing) {
47-
if (typeof thing === 'function') {
48-
throw new DevalueError(`Cannot stringify a function`, keys);
49-
}
50-
5147
if (thing === undefined) return UNDEFINED;
5248
if (Number.isNaN(thing)) return NAN;
5349
if (thing === Infinity) return POSITIVE_INFINITY;
@@ -67,6 +63,10 @@ export function stringify(value, reducers) {
6763
}
6864
}
6965

66+
if (typeof thing === 'function') {
67+
throw new DevalueError(`Cannot stringify a function`, keys);
68+
}
69+
7070
let str = '';
7171

7272
if (is_primitive(thing)) {

src/uneval.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@ export function uneval(value, replacer) {
2929

3030
/** @param {any} thing */
3131
function walk(thing) {
32-
if (typeof thing === 'function') {
33-
throw new DevalueError(`Cannot stringify a function`, keys);
34-
}
35-
3632
if (!is_primitive(thing)) {
3733
if (counts.has(thing)) {
3834
counts.set(thing, counts.get(thing) + 1);
@@ -50,6 +46,10 @@ export function uneval(value, replacer) {
5046
}
5147
}
5248

49+
if (typeof thing === 'function') {
50+
throw new DevalueError(`Cannot stringify a function`, keys);
51+
}
52+
5353
const type = get_type(thing);
5454

5555
switch (type) {

test/test.js

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,83 @@ const fixtures = {
602602
assert.ok(isNaN(obj.getDate()));
603603
}
604604
}
605-
])(new Date('invalid'))
605+
])(new Date('invalid')),
606+
607+
functions: (() => {
608+
// Simple function wrapper class for testing
609+
class FunctionRef {
610+
constructor(fn) {
611+
this.fn = fn;
612+
}
613+
}
614+
615+
const testFn = (x) => x * 2;
616+
617+
return [
618+
{
619+
name: 'Function wrapped in custom type',
620+
value: new FunctionRef(testFn),
621+
js: 'new FunctionRef((x) => x * 2)',
622+
json: '[["FunctionRef",1],"(x) => x * 2"]',
623+
replacer: (value, uneval) => {
624+
if (value instanceof FunctionRef) {
625+
// Serialize the function code directly as a string
626+
return `new FunctionRef(${value.fn.toString()})`;
627+
}
628+
},
629+
reducers: {
630+
FunctionRef: (value) => {
631+
if (value instanceof FunctionRef) {
632+
// Serialize the function code as a string
633+
return value.fn.toString();
634+
}
635+
}
636+
},
637+
revivers: {
638+
FunctionRef: (code) => {
639+
// Reconstruct the function from its string representation
640+
const fn = new Function('return ' + code)();
641+
return new FunctionRef(fn);
642+
}
643+
},
644+
validate: (result) => {
645+
assert.ok(result instanceof FunctionRef);
646+
assert.ok(typeof result.fn === 'function');
647+
// Test that the function works
648+
assert.equal(result.fn(5), 10);
649+
}
650+
},
651+
{
652+
name: 'Function in nested structure',
653+
value: { fn: testFn, nested: { data: 42 } },
654+
js: '{fn:(x) => x * 2,nested:{data:42}}',
655+
json: '[{"fn":1,"nested":3},["FunctionRef",2],"(x) => x * 2",{"data":4},42]',
656+
replacer: (value, uneval) => {
657+
if (typeof value === 'function') {
658+
// Serialize the function code directly
659+
return value.toString();
660+
}
661+
},
662+
reducers: {
663+
FunctionRef: (value) => {
664+
if (typeof value === 'function') {
665+
return value.toString();
666+
}
667+
}
668+
},
669+
revivers: {
670+
FunctionRef: (code) => {
671+
return new Function('return ' + code)();
672+
}
673+
},
674+
validate: (result) => {
675+
assert.ok(typeof result.fn === 'function');
676+
assert.equal(result.nested.data, 42);
677+
assert.equal(result.fn(3), 6);
678+
}
679+
}
680+
];
681+
})()
606682
};
607683

608684
for (const [name, tests] of Object.entries(fixtures)) {

0 commit comments

Comments
 (0)