Skip to content

Commit 1bfe642

Browse files
authored
refactor: move json-patch MessagePack encoding routine out of operations
Refactor `.encode()` method out of `AbstractOp` - create standalone `encodeOperationToMsgpack` function
2 parents c56b1ac + 743130b commit 1bfe642

31 files changed

+333
-266
lines changed
Lines changed: 333 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,344 @@
11
import {MsgPackEncoderFast as EncoderMessagePack} from '@jsonjoy.com/json-pack/lib/msgpack/MsgPackEncoderFast';
22
import type {Op} from '../../op';
3+
import type {AbstractOp} from '../../op/AbstractOp';
4+
import {OPCODE} from '../../constants';
5+
import type {
6+
OpAdd,
7+
OpRemove,
8+
OpReplace,
9+
OpCopy,
10+
OpMove,
11+
OpTest,
12+
OpStrIns,
13+
OpStrDel,
14+
OpFlip,
15+
OpInc,
16+
OpSplit,
17+
OpMerge,
18+
OpExtend,
19+
OpContains,
20+
OpDefined,
21+
OpEnds,
22+
OpIn,
23+
OpLess,
24+
OpMatches,
25+
OpMore,
26+
OpStarts,
27+
OpTestString,
28+
OpTestStringLen,
29+
OpTestType,
30+
OpType,
31+
OpUndefined,
32+
OpAnd,
33+
OpOr,
34+
OpNot,
35+
} from '../../op';
336

437
export class Encoder extends EncoderMessagePack {
538
public encode(patch: Op[]): Uint8Array {
639
this.writer.reset();
740
this.encodeArrayHeader(patch.length);
841
const length = patch.length;
9-
for (let i = 0; i < length; i++) patch[i].encode(this);
42+
for (let i = 0; i < length; i++) this.encodeOp(patch[i]);
1043
return this.writer.flush();
1144
}
45+
46+
private encodeOp(op: AbstractOp, parent?: AbstractOp): void {
47+
switch (op.code()) {
48+
case OPCODE.add: {
49+
const addOp = op as OpAdd;
50+
this.encodeArrayHeader(3);
51+
this.writer.u8(OPCODE.add);
52+
this.encodeArray(addOp.path as unknown[]);
53+
this.encodeAny(addOp.value);
54+
break;
55+
}
56+
57+
case OPCODE.remove: {
58+
const removeOp = op as OpRemove;
59+
const hasOldValue = removeOp.oldValue !== undefined;
60+
this.encodeArrayHeader(hasOldValue ? 3 : 2);
61+
this.writer.u8(OPCODE.remove);
62+
this.encodeArray(removeOp.path as unknown[]);
63+
if (hasOldValue) this.encodeAny(removeOp.oldValue);
64+
break;
65+
}
66+
67+
case OPCODE.replace: {
68+
const replaceOp = op as OpReplace;
69+
const hasOldValue = replaceOp.oldValue !== undefined;
70+
this.encodeArrayHeader(hasOldValue ? 4 : 3);
71+
this.writer.u8(OPCODE.replace);
72+
this.encodeArray(replaceOp.path as unknown[]);
73+
this.encodeAny(replaceOp.value);
74+
if (hasOldValue) this.encodeAny(replaceOp.oldValue);
75+
break;
76+
}
77+
78+
case OPCODE.copy: {
79+
const copyOp = op as OpCopy;
80+
this.encodeArrayHeader(3);
81+
this.writer.u8(OPCODE.copy);
82+
this.encodeArray(copyOp.path as unknown[]);
83+
this.encodeArray(copyOp.from as unknown[]);
84+
break;
85+
}
86+
87+
case OPCODE.move: {
88+
const moveOp = op as OpMove;
89+
this.encodeArrayHeader(3);
90+
this.writer.u8(OPCODE.move);
91+
this.encodeArray(moveOp.path as unknown[]);
92+
this.encodeArray(moveOp.from as unknown[]);
93+
break;
94+
}
95+
96+
case OPCODE.test: {
97+
const testOp = op as OpTest;
98+
this.encodeArrayHeader(testOp.not ? 4 : 3);
99+
this.writer.u8(OPCODE.test);
100+
this.encodeArray(parent ? testOp.path.slice(parent.path.length) : (testOp.path as unknown[]));
101+
this.encodeAny(testOp.value);
102+
if (testOp.not) this.writer.u8(1);
103+
break;
104+
}
105+
106+
case OPCODE.str_ins: {
107+
const strInsOp = op as OpStrIns;
108+
this.encodeArrayHeader(4);
109+
this.writer.u8(OPCODE.str_ins);
110+
this.encodeArray(strInsOp.path as unknown[]);
111+
this.encodeNumber(strInsOp.pos);
112+
this.encodeString(strInsOp.str);
113+
break;
114+
}
115+
116+
case OPCODE.str_del: {
117+
const strDelOp = op as OpStrDel;
118+
const hasStr = typeof strDelOp.str === 'string';
119+
this.encodeArrayHeader(hasStr ? 4 : 5);
120+
this.writer.u8(OPCODE.str_del);
121+
this.encodeArray(strDelOp.path as unknown[]);
122+
this.encodeNumber(strDelOp.pos);
123+
if (hasStr) {
124+
this.encodeString(strDelOp.str as string);
125+
} else {
126+
this.writer.u8(0);
127+
this.encodeNumber(strDelOp.len!);
128+
}
129+
break;
130+
}
131+
132+
case OPCODE.flip: {
133+
const flipOp = op as OpFlip;
134+
this.encodeArrayHeader(2);
135+
this.writer.u8(OPCODE.flip);
136+
this.encodeArray(flipOp.path as unknown[]);
137+
break;
138+
}
139+
140+
case OPCODE.inc: {
141+
const incOp = op as OpInc;
142+
this.encodeArrayHeader(3);
143+
this.writer.u8(OPCODE.inc);
144+
this.encodeArray(incOp.path as unknown[]);
145+
this.encodeNumber(incOp.inc);
146+
break;
147+
}
148+
149+
case OPCODE.split: {
150+
const splitOp = op as OpSplit;
151+
this.encodeArrayHeader(splitOp.props ? 4 : 3);
152+
this.writer.u8(OPCODE.split);
153+
this.encodeArray(splitOp.path as unknown[]);
154+
this.encodeNumber(splitOp.pos);
155+
if (splitOp.props) this.encodeObject(splitOp.props as Record<string, unknown>);
156+
break;
157+
}
158+
159+
case OPCODE.merge: {
160+
const mergeOp = op as OpMerge;
161+
this.encodeArrayHeader(mergeOp.props ? 4 : 3);
162+
this.writer.u8(OPCODE.merge);
163+
this.encodeArray(mergeOp.path as unknown[]);
164+
this.encodeNumber(mergeOp.pos);
165+
if (mergeOp.props) this.encodeAny(mergeOp.props);
166+
break;
167+
}
168+
169+
case OPCODE.extend: {
170+
const extendOp = op as OpExtend;
171+
const {deleteNull} = extendOp;
172+
this.encodeArrayHeader(deleteNull ? 4 : 3);
173+
this.writer.u8(OPCODE.extend);
174+
this.encodeArray(extendOp.path as unknown[]);
175+
this.encodeObject(extendOp.props);
176+
if (deleteNull) this.writer.u8(1);
177+
break;
178+
}
179+
180+
case OPCODE.contains: {
181+
const containsOp = op as OpContains;
182+
const ignoreCase = containsOp.ignore_case;
183+
this.encodeArrayHeader(ignoreCase ? 4 : 3);
184+
this.writer.u8(OPCODE.contains);
185+
this.encodeArray(parent ? containsOp.path.slice(parent.path.length) : (containsOp.path as unknown[]));
186+
this.encodeString(containsOp.value);
187+
if (ignoreCase) this.writer.u8(1);
188+
break;
189+
}
190+
191+
case OPCODE.defined: {
192+
const definedOp = op as OpDefined;
193+
this.encodeArrayHeader(2);
194+
this.writer.u8(OPCODE.defined);
195+
this.encodeArray(parent ? definedOp.path.slice(parent.path.length) : (definedOp.path as unknown[]));
196+
break;
197+
}
198+
199+
case OPCODE.ends: {
200+
const endsOp = op as OpEnds;
201+
const ignoreCase = endsOp.ignore_case;
202+
this.encodeArrayHeader(ignoreCase ? 4 : 3);
203+
this.writer.u8(OPCODE.ends);
204+
this.encodeArray(parent ? endsOp.path.slice(parent.path.length) : (endsOp.path as unknown[]));
205+
this.encodeString(endsOp.value);
206+
if (ignoreCase) this.writer.u8(1);
207+
break;
208+
}
209+
210+
case OPCODE.in: {
211+
const inOp = op as OpIn;
212+
this.encodeArrayHeader(3);
213+
this.writer.u8(OPCODE.in);
214+
this.encodeArray(parent ? inOp.path.slice(parent.path.length) : (inOp.path as unknown[]));
215+
this.encodeArray(inOp.value);
216+
break;
217+
}
218+
219+
case OPCODE.less: {
220+
const lessOp = op as OpLess;
221+
this.encodeArrayHeader(3);
222+
this.writer.u8(OPCODE.less);
223+
this.encodeArray(parent ? lessOp.path.slice(parent.path.length) : (lessOp.path as unknown[]));
224+
this.encodeNumber(lessOp.value);
225+
break;
226+
}
227+
228+
case OPCODE.matches: {
229+
const matchesOp = op as OpMatches;
230+
const ignoreCase = matchesOp.ignore_case;
231+
this.encodeArrayHeader(ignoreCase ? 4 : 3);
232+
this.writer.u8(OPCODE.matches);
233+
this.encodeArray(parent ? matchesOp.path.slice(parent.path.length) : (matchesOp.path as unknown[]));
234+
this.encodeString(matchesOp.value);
235+
if (ignoreCase) this.writer.u8(1);
236+
break;
237+
}
238+
239+
case OPCODE.more: {
240+
const moreOp = op as OpMore;
241+
this.encodeArrayHeader(3);
242+
this.writer.u8(OPCODE.more);
243+
this.encodeArray(parent ? moreOp.path.slice(parent.path.length) : (moreOp.path as unknown[]));
244+
this.encodeNumber(moreOp.value);
245+
break;
246+
}
247+
248+
case OPCODE.starts: {
249+
const startsOp = op as OpStarts;
250+
const ignoreCase = startsOp.ignore_case;
251+
this.encodeArrayHeader(ignoreCase ? 4 : 3);
252+
this.writer.u8(OPCODE.starts);
253+
this.encodeArray(parent ? startsOp.path.slice(parent.path.length) : (startsOp.path as unknown[]));
254+
this.encodeString(startsOp.value);
255+
if (ignoreCase) this.writer.u8(1);
256+
break;
257+
}
258+
259+
case OPCODE.test_type: {
260+
const testTypeOp = op as OpTestType;
261+
this.encodeArrayHeader(3);
262+
this.writer.u8(OPCODE.test_type);
263+
this.encodeArray(parent ? testTypeOp.path.slice(parent.path.length) : (testTypeOp.path as unknown[]));
264+
this.encodeArray(testTypeOp.type);
265+
break;
266+
}
267+
268+
case OPCODE.test_string: {
269+
const testStringOp = op as OpTestString;
270+
this.encodeArrayHeader(testStringOp.not ? 5 : 4);
271+
this.writer.u8(OPCODE.test_string);
272+
this.encodeArray(parent ? testStringOp.path.slice(parent.path.length) : (testStringOp.path as unknown[]));
273+
this.encodeNumber(testStringOp.pos);
274+
this.encodeString(testStringOp.str);
275+
if (testStringOp.not) this.writer.u8(1);
276+
break;
277+
}
278+
279+
case OPCODE.test_string_len: {
280+
const testStringLenOp = op as OpTestStringLen;
281+
this.encodeArrayHeader(testStringLenOp.not ? 4 : 3);
282+
this.writer.u8(OPCODE.test_string_len);
283+
this.encodeArray(parent ? testStringLenOp.path.slice(parent.path.length) : (testStringLenOp.path as unknown[]));
284+
this.encodeNumber(testStringLenOp.len);
285+
if (testStringLenOp.not) this.writer.u8(1);
286+
break;
287+
}
288+
289+
case OPCODE.type: {
290+
const typeOp = op as OpType;
291+
this.encodeArrayHeader(3);
292+
this.writer.u8(OPCODE.type);
293+
this.encodeArray(parent ? typeOp.path.slice(parent.path.length) : (typeOp.path as unknown[]));
294+
this.encodeString(typeOp.value);
295+
break;
296+
}
297+
298+
case OPCODE.undefined: {
299+
const undefinedOp = op as OpUndefined;
300+
this.encodeArrayHeader(2);
301+
this.writer.u8(OPCODE.undefined);
302+
this.encodeArray(parent ? undefinedOp.path.slice(parent.path.length) : (undefinedOp.path as unknown[]));
303+
break;
304+
}
305+
306+
case OPCODE.and: {
307+
const andOp = op as OpAnd;
308+
const path = parent ? andOp.path.slice(parent.path.length) : andOp.path;
309+
this.encodeArrayHeader(3);
310+
this.writer.u8(OPCODE.and);
311+
this.encodeArray(path as unknown[]);
312+
const length = andOp.ops.length;
313+
this.encodeArrayHeader(length);
314+
for (let i = 0; i < length; i++) this.encodeOp(andOp.ops[i], andOp);
315+
break;
316+
}
317+
318+
case OPCODE.not: {
319+
const notOp = op as OpNot;
320+
this.encodeArrayHeader(3);
321+
this.writer.u8(OPCODE.not);
322+
this.encodeArray(parent ? notOp.path.slice(parent.path.length) : (notOp.path as unknown[]));
323+
const length = notOp.ops.length;
324+
this.encodeArrayHeader(length);
325+
for (let i = 0; i < length; i++) this.encodeOp(notOp.ops[i], notOp);
326+
break;
327+
}
328+
329+
case OPCODE.or: {
330+
const orOp = op as OpOr;
331+
this.encodeArrayHeader(3);
332+
this.writer.u8(OPCODE.or);
333+
this.encodeArray(parent ? orOp.path.slice(parent.path.length) : (orOp.path as unknown[]));
334+
const length = orOp.ops.length;
335+
this.encodeArrayHeader(length);
336+
for (let i = 0; i < length; i++) this.encodeOp(orOp.ops[i], orOp);
337+
break;
338+
}
339+
340+
default:
341+
throw new Error(`Unknown operation code: ${op.code()}`);
342+
}
343+
}
12344
}

src/json-patch/op/AbstractOp.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type {Path} from '@jsonjoy.com/json-pointer';
33
import type {OpType} from '../opcodes';
44
import type {Operation} from '../types';
55
import type {OPCODE} from '../constants';
6-
import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack';
76

87
export abstract class AbstractOp<O extends OpType = OpType> {
98
public readonly from?: Path;
@@ -15,5 +14,4 @@ export abstract class AbstractOp<O extends OpType = OpType> {
1514
abstract apply(doc: unknown): {doc: unknown; old?: unknown};
1615
abstract toJson(parent?: AbstractOp): Operation;
1716
abstract toCompact(parent: undefined | AbstractOp, verbose: boolean): CompactOpBase;
18-
abstract encode(encoder: IMessagePackEncoder, parent?: AbstractOp): void;
1917
}

src/json-patch/op/OpAdd.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type {OperationAdd} from '../types';
44
import {find, type Path, formatJsonPointer} from '@jsonjoy.com/json-pointer';
55
import {OPCODE} from '../constants';
66
import {clone as deepClone} from '@jsonjoy.com/util/lib/json-clone/clone';
7-
import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack';
87

98
/**
109
* @category JSON Patch
@@ -51,11 +50,4 @@ export class OpAdd extends AbstractOp<'add'> {
5150
const opcode: OPCODE_ADD = verbose ? 'add' : OPCODE.add;
5251
return [opcode, this.path, this.value];
5352
}
54-
55-
public encode(encoder: IMessagePackEncoder) {
56-
encoder.encodeArrayHeader(3);
57-
encoder.writer.u8(OPCODE.add);
58-
encoder.encodeArray(this.path as unknown[]);
59-
encoder.encodeAny(this.value);
60-
}
6153
}

0 commit comments

Comments
 (0)