Skip to content

Commit 55fe047

Browse files
authored
Merge pull request #41 from athombv/master
MMIP
2 parents 6eed7bf + 7ae2320 commit 55fe047

4 files changed

Lines changed: 135 additions & 3 deletions

File tree

lib/Struct.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,34 @@
11
'use strict';
22

3-
function Struct(name, defs) {
3+
/**
4+
* Creates a named Struct class with typed fields, supporting fixed and variable-length binary
5+
* encoding.
6+
*
7+
* @param {string} name - The name of the generated class.
8+
* @param {Object.<string, DataType>} defs - An object mapping field names to their data type
9+
* definitions. Each data type must expose `length`, `defaultValue`, `fromBuffer`, and `toBuffer`.
10+
* @param {Object} [opts] - Optional configuration.
11+
* @param {'default'|'skip'} [opts.encodeMissingFieldsBehavior='default'] - Controls how fields
12+
* with `undefined` values are handled during encoding. `'default'` fills them with the type's
13+
* default value; `'skip'` omits them entirely from the encoded output.
14+
* @returns {typeof Struct} The generated Struct class.
15+
*/
16+
function Struct(name, defs, opts) {
417
Object.seal(defs);
18+
const encodeMissingFieldsBehavior = (opts && opts.encodeMissingFieldsBehavior) || 'default';
519
let size = 0;
620
let varsize = false;
21+
722
for (const dt of Object.values(defs)) {
823
if (typeof dt.length === 'number' && dt.length > 0) {
924
size += dt.length;
1025
} else varsize = true;
1126
}
27+
28+
if (encodeMissingFieldsBehavior === 'skip') {
29+
varsize = true;
30+
}
31+
1232
const r = {
1333
[name]: class {
1434

@@ -20,6 +40,10 @@ function Struct(name, defs) {
2040
}
2141
// eslint-disable-next-line no-restricted-syntax
2242
for (const key in defs) {
43+
if (encodeMissingFieldsBehavior === 'skip' && typeof this[key] === 'undefined') {
44+
continue;
45+
}
46+
2347
if (typeof props[key] === 'undefined') {
2448
this[key] = defs[key].defaultValue;
2549
}
@@ -102,6 +126,7 @@ function Struct(name, defs) {
102126

103127
// eslint-disable-next-line guard-for-in,no-restricted-syntax
104128
for (const p in defs) {
129+
if (encodeMissingFieldsBehavior === 'skip' && typeof this[p] === 'undefined') continue;
105130
// eslint-disable-next-line no-shadow
106131
let varsize = defs[p].length;
107132
if (defs[p].length <= 0) {

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@athombv/data-types",
3-
"version": "1.1.4",
3+
"version": "1.2.0",
44
"description": "Binary Data Parsers",
55
"main": "index.js",
66
"types": "index.d.ts",

test/Struct.test.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,113 @@ describe('Struct', function() {
3232
it('should parse test data to buffer', function() {
3333
assert(dataBuf.equals(Buffer.from('0474657374f40103040100020003000400020201', 'hex')));
3434
});
35+
it('should encode missing fields with default values by default', function() {
36+
const S = Struct('DefaultMissing', {
37+
a: DataTypes.uint8,
38+
b: DataTypes.uint8,
39+
});
40+
const instance = new S({ a: 42 });
41+
const buf = instance.toBuffer();
42+
43+
// Both fields encoded: a=42, b=0 (default)
44+
assert.equal(buf.length, 2);
45+
assert.equal(buf[0], 42);
46+
assert.equal(buf[1], 0);
47+
});
48+
it('should skip missing fields in nested struct without padding', function() {
49+
const Inner = Struct('InnerSkip', {
50+
x: DataTypes.uint8,
51+
y: DataTypes.uint8,
52+
}, { encodeMissingFieldsBehavior: 'skip' });
53+
54+
const Outer = Struct('OuterWithInner', {
55+
id: DataTypes.uint8,
56+
inner: Inner,
57+
});
58+
59+
const instance = new Outer({
60+
id: 5,
61+
inner: { x: 10 },
62+
});
63+
64+
const buf = instance.toBuffer();
65+
// id (1 byte) + inner.x (1 byte), no padding for missing inner.y
66+
assert.equal(buf.length, 2);
67+
assert.equal(buf[0], 5);
68+
assert.equal(buf[1], 10);
69+
});
70+
71+
it('should skip missing fields in array of structs without padding', function() {
72+
const Item = Struct('ItemSkip', {
73+
a: DataTypes.uint8,
74+
b: DataTypes.uint8,
75+
}, { encodeMissingFieldsBehavior: 'skip' });
76+
77+
const S = Struct('ArrayContainer', {
78+
items: DataTypes.Array8(Item),
79+
});
80+
81+
const instance = new S({
82+
items: [
83+
{ a: 1 },
84+
{ a: 2, b: 20 },
85+
{ b: 30 },
86+
],
87+
});
88+
89+
const buf = instance.toBuffer();
90+
// Array8: 1 byte length + items
91+
// item1: 1 byte (a=1)
92+
// item2: 2 bytes (a=2, b=20)
93+
// item3: 1 byte (b=30)
94+
// Total: 1 + 1 + 2 + 1 = 5
95+
assert.equal(buf.length, 5);
96+
assert.equal(buf[0], 3); // array length
97+
assert.equal(buf[1], 1); // item1.a
98+
assert.equal(buf[2], 2); // item2.a
99+
assert.equal(buf[3], 20); // item2.b
100+
assert.equal(buf[4], 30); // item3.b
101+
});
102+
103+
it('should handle mixed skip and default behavior in nested structures', function() {
104+
const SkipInner = Struct('SkipInner', {
105+
x: DataTypes.uint8,
106+
y: DataTypes.uint8,
107+
}, { encodeMissingFieldsBehavior: 'skip' });
108+
109+
const DefaultInner = Struct('DefaultInner', {
110+
p: DataTypes.uint8,
111+
q: DataTypes.uint8,
112+
});
113+
114+
const Outer = Struct('MixedOuter', {
115+
skip: SkipInner,
116+
default: DefaultInner,
117+
});
118+
119+
const instance = new Outer({
120+
skip: { x: 5 },
121+
default: { p: 10 },
122+
});
123+
124+
const buf = instance.toBuffer();
125+
// skip: 1 byte (x=5)
126+
// default: 2 bytes (p=10, q=0)
127+
// Total: 3
128+
assert.equal(buf.length, 3);
129+
assert.equal(buf[0], 5);
130+
assert.equal(buf[1], 10);
131+
assert.equal(buf[2], 0);
132+
});
133+
it('should produce identical output with skip option when all fields provided', function() {
134+
const defsA = { a: DataTypes.uint8, b: DataTypes.uint8 };
135+
const defsB = { a: DataTypes.uint8, b: DataTypes.uint8 };
136+
const Default = Struct('AllFieldsDefault', defsA);
137+
const Skip = Struct('AllFieldsSkip', defsB, { encodeMissingFieldsBehavior: 'skip' });
138+
const d = new Default({ a: 1, b: 2 });
139+
const s = new Skip({ a: 1, b: 2 });
140+
assert(d.toBuffer().equals(s.toBuffer()));
141+
});
35142
it('should parse test data from buffer', function() {
36143
const refData = TestStruct.fromBuffer(dataBuf);
37144

0 commit comments

Comments
 (0)