Skip to content

Commit c2ecbd9

Browse files
committed
Add detectArrays option to deserializer
1 parent 16b5824 commit c2ecbd9

File tree

3 files changed

+149
-14
lines changed

3 files changed

+149
-14
lines changed

packages/kv/src/serializer.test.ts

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,79 @@ test("Deserialize Item", () => {
5959
const result = nodes[0];
6060

6161
expect(result).toBeDefined();
62-
expect(result!.prop1).toBe("string val");
63-
expect(result!.sub).toBeDefined();
62+
expect((result! as any).prop1).toBe("string val");
63+
expect((result! as any).sub).toBeDefined();
6464
expect(result).toHaveProperty("sub.more", 10);
6565
expect(result).toHaveProperty("sub.stuff", "but nested");
6666
expect(result).toHaveProperty("sub.boolprop", false);
6767
expect(result).toHaveProperty("sub.boolpropa", true);
6868
expect(result).toHaveProperty("another prop", ["with", "multiple", "values"]);
6969
expect(result).toHaveProperty("floatprop", 4.5);
7070
expect(result).toHaveProperty("intprop", 90000);
71+
});
72+
73+
test("Deserialize mdlinfo", () => {
74+
const doc = parseText(`"_hr/props/pedestal_button"
75+
{
76+
"general"
77+
{
78+
"name" "_hr/props/pedestal_button"
79+
"filename" "/home/stefan/Projects/PortalRevolution2/game/revolution/models/_hr/props/pedestal_button.mdl"
80+
"id" "1414743113"
81+
"version" "49"
82+
"checksum" "1323378053"
83+
"eye_pos" "0.000000 0.000000 0.000000"
84+
"illum_pos" "0.000000 0.000000 23.486897"
85+
"hull_min" "-12.849852 -12.849851 -0.255160"
86+
"hull_max" "12.849852 12.849851 47.038059"
87+
"bone_count" "3"
88+
"attachment_count" "0"
89+
"surfaceprop" "default"
90+
}
91+
"materials"
92+
{
93+
"0" "pedestal_button_blue"
94+
"1" "pedestal_button_orange"
95+
"2" "pedestal_button"
96+
}
97+
"cdmaterials"
98+
{
99+
"0" "_hr/models/props/"
100+
}
101+
"mdlkeyvalue"
102+
{
103+
"qc_path"
104+
{
105+
"value" "models\\_hr\\props\\pedestal_button\\pedestal_button.qc"
106+
}
107+
}
108+
}`);
109+
110+
const nodes = KvSerializer.deserialize(doc, { detectArrays: true });
111+
expect(nodes).toHaveLength(1);
112+
113+
expect(nodes[0]).toHaveProperty("general.name", "_hr/props/pedestal_button");
114+
expect(nodes[0]).toHaveProperty("general.filename", "/home/stefan/Projects/PortalRevolution2/game/revolution/models/_hr/props/pedestal_button.mdl");
115+
expect(nodes[0]).toHaveProperty("general.id", 1414743113);
116+
expect(nodes[0]).toHaveProperty("general.version", 49);
117+
expect(nodes[0]).toHaveProperty("general.checksum", 1323378053);
118+
expect(nodes[0]).toHaveProperty("general.eye_pos", "0.000000 0.000000 0.000000");
119+
expect(nodes[0]).toHaveProperty("general.illum_pos", "0.000000 0.000000 23.486897");
120+
expect(nodes[0]).toHaveProperty("general.hull_min", "-12.849852 -12.849851 -0.255160");
121+
expect(nodes[0]).toHaveProperty("general.hull_max", "12.849852 12.849851 47.038059");
122+
expect(nodes[0]).toHaveProperty("general.bone_count", 3);
123+
expect(nodes[0]).toHaveProperty("general.attachment_count", 0);
124+
expect(nodes[0]).toHaveProperty("general.surfaceprop", "default");
125+
126+
expect(Array.isArray((nodes[0] as any).materials)).toBeTruthy();
127+
expect((nodes[0] as any).materials).toHaveLength(3);
128+
expect((nodes[0] as any).materials[0]).toBe("pedestal_button_blue");
129+
expect((nodes[0] as any).materials[1]).toBe("pedestal_button_orange");
130+
expect((nodes[0] as any).materials[2]).toBe("pedestal_button");
131+
132+
expect(Array.isArray((nodes[0] as any).cdmaterials)).toBeTruthy();
133+
expect((nodes[0] as any).cdmaterials).toHaveLength(1);
134+
expect((nodes[0] as any).cdmaterials[0]).toBe("_hr/models/props/");
135+
136+
expect(nodes[0]).toHaveProperty("mdlkeyvalue.qc_path.value", "models\\_hr\\props\\pedestal_button\\pedestal_button.qc");
71137
});

packages/kv/src/serializer.ts

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import {Document, Item, Literal} from "./parser-types";
2+
import {isFloatValue, isIntegerValue} from "./string-util";
23

34

45
type KvNodeValue = string | number | boolean;
5-
type KvNode = { [key: string]: KvNodeValue | KvNodeValue[] | KvNode };
6+
type KvNodeRecord = { [key: string]: KvNodeValue | KvNodeValue[] | KvNode };
7+
type KvNodeArray = (KvNode | KvNodeValue)[];
8+
type KvNode = KvNodeRecord | KvNodeArray;
9+
10+
export interface KvSerializeOptions {
11+
name?: string;
12+
indentSpaces?: number;
13+
indentTabs?: boolean;
14+
}
615

16+
export interface KvDeserializeOptions {
17+
detectArrays?: boolean;
18+
}
719

820
export const KvSerializer = {
921

10-
serialize(obj: Record<string, unknown>, options?: { name?: string, indentSpaces?: number, indentTabs?: boolean }, nestedLevel = 0): string {
22+
serialize(obj: Record<string, unknown>, options?: KvSerializeOptions, nestedLevel = 0): string {
1123
let indentObjStr = "";
1224
let indentStrKv = "";
1325
let indentChar = "";
@@ -44,8 +56,12 @@ export const KvSerializer = {
4456
return fullStr;
4557
},
4658

47-
deserialize(doc: Document): KvNode[] {
48-
return doc.getRootItems().map(_deserializeItem).filter(i => i !== undefined) as KvNode[];
59+
deserialize(doc: Document, options?: KvDeserializeOptions): KvNode[] {
60+
const nodes = doc.getRootItems().map(_deserializeItem).filter(i => i !== undefined) as KvNode[];
61+
if(options?.detectArrays) {
62+
return _recursiveTransformNodesToArray(nodes);
63+
}
64+
return nodes;
4965
}
5066
};
5167

@@ -86,15 +102,66 @@ function _deserializeItemValue(vals: Literal[]): KvNodeValue | KvNodeValue[] | u
86102
}
87103
}
88104

89-
function _transformKvValue(val: string): KvNodeValue {
90-
const float = Number.parseFloat(val);
91-
if(!Number.isNaN(float)) {
92-
return float;
105+
function _recursiveTransformNodesToArray(nodes: KvNode[]): KvNode[] {
106+
const result: KvNode[] = [];
107+
for (const node of nodes) {
108+
result.push(_transformNodeToArray(node));
109+
}
110+
return result;
111+
}
112+
113+
function _transformNodeToArray(node: KvNode): KvNode {
114+
if (typeof node !== "object") {
115+
return node;
116+
}
117+
118+
const resultNode: KvNodeRecord = {};
119+
const entries = Object.entries(node);
120+
for (const [k, v] of entries) {
121+
if(typeof v === "object") {
122+
const newV = _transformNodeToArray(v);
123+
if (_isArrayNode(newV)) {
124+
resultNode[k] = Array.from(Object.values(newV));
125+
} else {
126+
resultNode[k] = v;
127+
}
128+
} else {
129+
resultNode[k] = v;
130+
}
93131
}
94132

95-
const int = Number.parseInt(val);
96-
if(!Number.isNaN(int)) {
97-
return int;
133+
return resultNode;
134+
}
135+
136+
function _isArrayNode(node: KvNode): boolean {
137+
let prev = -1;
138+
for(const key of Object.keys(node)) {
139+
const intKey = Number.parseInt(key);
140+
if(Number.isNaN(intKey)) {
141+
return false;
142+
}
143+
144+
if(intKey !== prev + 1) {
145+
return false;
146+
}
147+
148+
prev += 1;
149+
}
150+
151+
return true;
152+
}
153+
154+
function _transformKvValue(val: string): KvNodeValue {
155+
if(isFloatValue(val)) {
156+
const float = Number.parseFloat(val);
157+
if(!Number.isNaN(float)) {
158+
return float;
159+
}
160+
} else if(isIntegerValue(val)) {
161+
const int = Number.parseInt(val);
162+
if(!Number.isNaN(int)) {
163+
return int;
164+
}
98165
}
99166

100167
if(val === "true") {

packages/kv/src/tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
"declaration": true,
2121
},
2222
"exclude": [
23-
"node_modules"
23+
"node_modules",
24+
"./**/*.test.ts",
25+
"./**/*.bench.ts"
2426
]
2527
}

0 commit comments

Comments
 (0)