Skip to content

Commit 35238aa

Browse files
committed
{feature} try to retain the original in serialization
Try to retain the original order of entries when serializing an element that came from deserialization. This only applies to entries, not children, because the order of children might matter. The original order of properties is retained completely. The order of arguments cannot be, for the simple reason that the order of arguments is likely to be significant. The interleaving of properties and arguments is retained.
1 parent 229fe3f commit 35238aa

File tree

2 files changed

+89
-9
lines changed

2 files changed

+89
-9
lines changed

src/dessert/serialization/serialize.js

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ export function serialize(name, serializer, ...parameters) {
5959
/** @type {import("../deserialization/types.js").DeserializationContext | null | undefined} */
6060
let source;
6161

62+
/** @type {Entry[]} */
63+
let entries = [];
64+
/** @type {Set<Entry>} */
65+
let entriesToRemove = new Set();
66+
6267
/** @type {Entry[]} */
6368
let existingArguments = [];
6469
/** @type {Map<string, Entry>} */
@@ -76,7 +81,7 @@ export function serialize(name, serializer, ...parameters) {
7681
throw new Error("The source function can only be called once");
7782
}
7883

79-
if (node.entries.length || node.hasChildren()) {
84+
if (entries.length || node.hasChildren()) {
8085
throw new Error(
8186
"The source function can only be called at the start of a serialize function",
8287
);
@@ -91,8 +96,14 @@ export function serialize(name, serializer, ...parameters) {
9196
node = sourceNode.clone({shallow: true});
9297
node.setName(typeof name === "string" ? name : "-");
9398

94-
existingArguments = sourceNode.getArgumentEntries();
95-
existingProperties = sourceNode.getPropertyEntryMap();
99+
entries = sourceNode.entries.map((entry) => entry.clone());
100+
entriesToRemove = new Set(entries);
101+
existingArguments = entries.filter((entry) => entry.name == null);
102+
existingProperties = new Map(
103+
entries.flatMap((entry) =>
104+
entry.name ? [[entry.name.name, entry]] : [],
105+
),
106+
);
96107
},
97108

98109
argument: tagged((tag, value) => {
@@ -102,16 +113,17 @@ export function serialize(name, serializer, ...parameters) {
102113
);
103114
}
104115

105-
let argument = existingArguments.shift()?.clone();
116+
let argument = existingArguments.shift();
106117

107118
if (argument) {
119+
entriesToRemove.delete(argument);
108120
argument.setValue(value);
109121
} else {
110122
argument = Entry.createArgument(value);
123+
entries.push(argument);
111124
}
112125

113126
argument.setTag(tag);
114-
node.entries.push(argument);
115127
}),
116128

117129
property: tagged((tag, key, value) => {
@@ -121,16 +133,17 @@ export function serialize(name, serializer, ...parameters) {
121133
);
122134
}
123135

124-
let property = existingProperties.get(key)?.clone();
136+
let property = existingProperties.get(key);
125137

126138
if (property) {
139+
entriesToRemove.delete(property);
127140
property.setValue(value);
128141
} else {
129142
property = Entry.createProperty(key, value);
143+
entries.push(property);
130144
}
131145

132146
property.setTag(tag);
133-
node.entries.push(property);
134147
}),
135148

136149
child: /** @type {t.SerializationContext["child"]} */ (
@@ -150,6 +163,12 @@ export function serialize(name, serializer, ...parameters) {
150163
),
151164

152165
json(value) {
166+
// We don't properly keep track of property / argument order yet in serializeJson
167+
entries = node.entries = entries.filter(
168+
(entry) => !entriesToRemove.has(entry),
169+
);
170+
entriesToRemove = new Set();
171+
153172
serializeJson(
154173
!isSerializingDocument,
155174
value,
@@ -167,6 +186,8 @@ export function serialize(name, serializer, ...parameters) {
167186
// @ts-expect-error If only typescript supported @overload with rest parameters
168187
run(serializer, ...parameters);
169188

189+
node.entries = entries.filter((entry) => !entriesToRemove.has(entry));
190+
170191
return typeof name !== "string" ? (node.children ?? new Document()) : node;
171192
}
172193

test/dessert/serialize.js

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import assert from "node:assert/strict";
22
import {test} from "uvu";
33

4-
import {concat, serialize} from "../../src/dessert.js";
4+
import {concat, deserialize, serialize} from "../../src/dessert.js";
55
import {clearFormat, format, parse} from "../../src/index.js";
66

7-
/** @import {SerializationContext} from "../../src/dessert.js"; */
7+
/** @import {DeserializationContext, SerializationContext} from "../../src/dessert.js"; */
88
/** @import {JsonValue} from "../../src/json.js"; */
99

1010
test("simple", () => {
@@ -58,4 +58,63 @@ node type=json 0 1 2
5858
);
5959
});
6060

61+
test("order of properties", () => {
62+
class Test {
63+
/** @type {DeserializationContext} */
64+
#ctx;
65+
66+
first;
67+
second;
68+
69+
/**
70+
* @param {string} first
71+
* @param {string} second
72+
*/
73+
constructor(first, second) {
74+
this.first = first;
75+
this.second = second;
76+
}
77+
78+
/** @param {DeserializationContext} ctx */
79+
static deserialize(ctx) {
80+
const value = new Test(
81+
ctx.property.required("first", "string"),
82+
ctx.property.required("second", "string"),
83+
);
84+
value.#ctx = ctx;
85+
return value;
86+
}
87+
88+
/** @param {SerializationContext} ctx */
89+
serialize(ctx) {
90+
ctx.source(this.#ctx);
91+
92+
ctx.property("first", this.first);
93+
ctx.property("second", this.second);
94+
}
95+
}
96+
97+
let node = parse(`node first=first second=second;`, {as: "node"});
98+
let test = deserialize(node, Test);
99+
100+
test.second = "first";
101+
test.first = "second";
102+
103+
assert.equal(
104+
format(serialize("node", test)),
105+
"node first=second second=first;",
106+
);
107+
108+
node = parse(`node second=second first=first;`, {as: "node"});
109+
test = deserialize(node, Test);
110+
111+
test.second = "first";
112+
test.first = "second";
113+
114+
assert.equal(
115+
format(serialize("node", test)),
116+
"node second=first first=second;",
117+
);
118+
});
119+
61120
test.run();

0 commit comments

Comments
 (0)