Skip to content

Commit

Permalink
fix: serialization of signals containing null and undefined values (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
lucacasonato authored Oct 31, 2024
1 parent 22af152 commit 8ced083
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 12 deletions.
45 changes: 42 additions & 3 deletions src/jsonify/custom_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ Deno.test("custom parse - Point", () => {
}

const str = stringify(new Point(30, 40), {
Point: (value) => value instanceof Point ? [value.x, value.y] : undefined,
Point: (value) =>
value instanceof Point ? { value: [value.x, value.y] } : undefined,
});

expect(str).toEqual('[["Point",1],[2,3],30,40]');
Expand All @@ -35,18 +36,56 @@ Deno.test("custom stringify - Signals", () => {
const s = signal(2);
expect(stringify(s, {
Signal: (s2: unknown) => {
return s2 instanceof Signal ? s2.peek() : undefined;
return s2 instanceof Signal ? { value: s2.peek() } : undefined;
},
})).toEqual(
'[["Signal",1],2]',
);
});

Deno.test("custom parse - Signals with null value", () => {
const res = parse<Signal>('[["Signal",-2]]', {
Signal: (value) => signal(value),
});
expect(res).toBeInstanceOf(Signal);
expect(res.peek()).toEqual(null);
});

Deno.test("custom stringify - Signals with null value", () => {
const s = signal(null);
expect(stringify(s, {
Signal: (s2: unknown) => {
return s2 instanceof Signal ? { value: s2.peek() } : undefined;
},
})).toEqual(
'[["Signal",-2]]',
);
});

Deno.test("custom parse - Signals with undefined value", () => {
const res = parse<Signal>('[["Signal",-1]]', {
Signal: (value) => signal(value),
});
expect(res).toBeInstanceOf(Signal);
expect(res.peek()).toEqual(undefined);
});

Deno.test("custom stringify - Signals with undefined value", () => {
const s = signal(undefined);
expect(stringify(s, {
Signal: (s2: unknown) => {
return s2 instanceof Signal ? { value: s2.peek() } : undefined;
},
})).toEqual(
'[["Signal",-1]]',
);
});

Deno.test("custom stringify - referenced Signals", () => {
const s = signal(2);
expect(stringify([s, s], {
Signal: (s2: unknown) => {
return s2 instanceof Signal ? s2.peek() : undefined;
return s2 instanceof Signal ? { value: s2.peek() } : undefined;
},
})).toEqual(
'[[1,1],["Signal",2],2]',
Expand Down
28 changes: 26 additions & 2 deletions src/jsonify/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,32 @@ function unpack(
if (custom !== undefined && name in custom) {
const fn = custom[name];
const ref = current[1];
unpack(arr, hydrated, ref, custom);
const value = hydrated[ref];
let value;
if (ref < 0) {
switch (ref) {
case UNDEFINED:
value = undefined;
break;
case NULL:
value = null;
break;
case NAN:
value = NaN;
break;
case INFINITY_POS:
value = Infinity;
break;
case INFINITY_NEG:
value = -Infinity;
break;
case ZERO_NEG:
value = -0;
break;
}
} else {
unpack(arr, hydrated, ref, custom);
value = hydrated[ref];
}
hydrated[idx] = fn(value);
return;
}
Expand Down
11 changes: 7 additions & 4 deletions src/jsonify/stringify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import {
} from "./constants.ts";
import { HOLE } from "./constants.ts";

// deno-lint-ignore no-explicit-any
export type Stringifiers = Record<string, (value: any) => any>;
export type Stringifiers = Record<
string,
// deno-lint-ignore no-explicit-any
(value: any) => { value: any } | undefined
>;

/**
* Serializes the following:
Expand Down Expand Up @@ -94,8 +97,8 @@ function serializeInner(
const res = fn(value);
if (res === undefined) continue;

serializeInner(out, indexes, res, custom);
str = `["${k}",${idx + 1}]`;
const innerIdx = serializeInner(out, indexes, res.value, custom);
str = `["${k}",${innerIdx}]`;
out[idx] = str;
return idx;
}
Expand Down
8 changes: 5 additions & 3 deletions src/runtime/server/preact_hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -369,14 +369,16 @@ function isVNode(x: any): x is VNode {

const stringifiers: Stringifiers = {
Signal: (value: unknown) => {
return isSignal(value) ? value.peek() : undefined;
return isSignal(value) ? { value: value.peek() } : undefined;
},
Slot: (value: unknown) => {
if (isVNode(value) && value.type === Slot) {
const props = value.props as SlotProps;
return {
name: props.name,
id: props.id,
value: {
name: props.name,
id: props.id,
},
};
}
},
Expand Down

0 comments on commit 8ced083

Please sign in to comment.