diff --git a/CHANGELOG.md b/CHANGELOG.md index 017b2ac..3129b66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # ⏩ vNext +- Add support for custom transformers on parsed values # v4.2.2 - Fix an issue where the new 53-bit oversize error is emitted when the `float` format is selected, even though it is diff --git a/README.md b/README.md index 910395a..a634c1c 100644 --- a/README.md +++ b/README.md @@ -550,6 +550,20 @@ class Type2Element extends BaseElement { Here `count` and `items.length` will always agree at all times, with the bonus that assigning `undefined` to the array is forbidden. +### Transformers + +It can be useful to have custom function that transforms the field value after deserialize or before serialize. +Use the `transformers` option to specify read or/and write function: + +```typescript +class Type2Element extends BaseElement { + @Field(version, { transformers: {read: v => v/100, write: v => v * 100 }) + version : number; +} +``` + +In this example version value `1.20` is the integer `120` on the stream. + ### Measurement It can be useful to measure the bitlength of a portion of a BitstreamElement. Such a measurement could be used when diff --git a/src/elements/array-serializer.ts b/src/elements/array-serializer.ts index 3d8b96c..6241b09 100644 --- a/src/elements/array-serializer.ts +++ b/src/elements/array-serializer.ts @@ -75,6 +75,11 @@ export class ArraySerializer implements Serializer { readNumber(); } } + if(field.options?.transformers?.read) { + elements.forEach((element, index) => { + elements[index] = field.options.transformers.read(element, field, parent); + }) + } } else { if (field.options.array.hasMore) { let i = 0; @@ -167,10 +172,15 @@ export class ArraySerializer implements Serializer { } } + for (let i = 0; i < length; ++i) { if (field?.options?.array?.type === Number) { let type = field.options?.number?.format ?? 'unsigned'; + if(field.options?.transformers?.write) { + value[i] = field.options.transformers.write(value[i],field,parent); + } + if (type === 'unsigned') writer.write(field.options.array.elementLength, value[i]); else if (type === 'signed') @@ -179,6 +189,10 @@ export class ArraySerializer implements Serializer { writer.writeFloat(field.options.array.elementLength, value[i]); } else { + if(field.options?.transformers?.write) { + Object.assign(value[i], field.options.transformers.write(value[i],field,parent)); + } + (value[i] as BitstreamElement).write(writer, { context: parent?.context }); } } diff --git a/src/elements/boolean-serializer.ts b/src/elements/boolean-serializer.ts index cb19f99..790e9da 100644 --- a/src/elements/boolean-serializer.ts +++ b/src/elements/boolean-serializer.ts @@ -15,17 +15,26 @@ export class BooleanSerializer implements Serializer { if (!reader.isAvailable(length)) yield { remaining: length, contextHint: () => summarizeField(field) }; - const numericValue = reader.readSync(length); + let numericValue = reader.readSync(length); + const trueValue = field?.options?.boolean?.true ?? 1; const falseValue = field?.options?.boolean?.false ?? 0; const mode = field?.options?.boolean?.mode ?? 'true-unless'; + let boolValue; if (mode === 'true-unless') - return numericValue !== falseValue; + boolValue = numericValue !== falseValue; else if (mode === 'false-unless') - return numericValue === trueValue; + boolValue = numericValue === trueValue; else if (mode === 'undefined') - return numericValue === trueValue ? true : numericValue === falseValue ? false : undefined; + boolValue = numericValue === trueValue ? true : numericValue === falseValue ? false : undefined; + + if ( field?.options?.transformers?.read ){ + boolValue = field.options.transformers.read(boolValue,field, parent); + } + + return boolValue; + } write(writer: BitstreamWriter, type : any, instance: any, field: FieldDefinition, value: any) { @@ -35,6 +44,10 @@ export class BooleanSerializer implements Serializer { const undefinedValue = field?.options?.boolean?.undefined ?? 0; let numericValue : number; + if ( field?.options?.transformers?.write ){ + value = field.options.transformers.write(value,field, instance); + } + if (value === void 0) numericValue = undefinedValue; else if (value) diff --git a/src/elements/buffer-serializer.ts b/src/elements/buffer-serializer.ts index 003fc12..9f36533 100644 --- a/src/elements/buffer-serializer.ts +++ b/src/elements/buffer-serializer.ts @@ -36,6 +36,11 @@ import { summarizeField } from "./utils"; break; } + if ( field?.options?.transformers?.read ){ + buffer = field.options.transformers.read(buffer,field, parent); + } + + return buffer; } @@ -56,6 +61,11 @@ import { summarizeField } from "./utils"; throw new Error(`BufferSerializer: Field ${String(field.name)}: Cannot encode a null buffer`); } + if ( field?.options?.transformers?.write ){ + value = field.options.transformers.write(value,field, parent); + } + + if (value.length > fieldLength) { if (truncate) { writer.writeBuffer(value.subarray(0, fieldLength)); diff --git a/src/elements/element.test.ts b/src/elements/element.test.ts index 42893db..e1d4045 100644 --- a/src/elements/element.test.ts +++ b/src/elements/element.test.ts @@ -115,6 +115,8 @@ describe('BitstreamElement', it => { }); }); + + it('can handle private fields', async () => { class CustomElement extends BitstreamElement { @Field(8) private byte1 : number; @@ -146,7 +148,7 @@ describe('BitstreamElement', it => { expect(b.field1).to.equal(1); expect(b.field2).to.equal(2); }); - + it('@Field() accepts single options when length is inferred', async () => { class CustomElement2 extends BitstreamElement { @Field(8) byte2: number; @@ -2357,4 +2359,360 @@ describe('BitstreamElement', it => { expect(value.elements[i].byte, `value at index ${i} should be ${i}`).to.equal(i); }); }); -}) \ No newline at end of file + describe(': Transformers', it => { + it('@Field() accepts read function', async () => { + class A extends BitstreamElement { + @Field(8,{transformers: {read:(v)=>v*2}}) byte1: number; + @Field(8,{transformers: {read:(v)=>v/10}}) byte2: number; + } + + let a = A.deserialize(Buffer.from([ 123, 124, 'G', 'o' ])); + + expect(a.byte1).to.equal(123*2); + expect(a.byte2).to.equal(124/10); + }); + it('@Field() accepts write function', async () => { + class A extends BitstreamElement { + @Field(8,{transformers: {write:(v)=>v/2}}) byte1: number; + @Field(8,{transformers: {write:(v)=>v*10}}) byte2: number; + } + + let a = new A(); + a.byte1 = 123*2; + a.byte2 = 124/10; + + let buf = Buffer.from(a.serialize()); + + expect(buf.readUInt8(0)).to.equal(123); + expect(buf.readUInt8(1)).to.equal(124); + }); + it('@Field() accept read and write functions', async () => { + class A extends BitstreamElement { + @Field(16,{number: {format: 'unsigned'},transformers: {write:(v)=>v/2, read:(v)=>v*2}}) num1: number; + @Field(16,{number: {format: 'unsigned'},transformers: {write:(v)=>v*10, read:(v)=>v/10}}) num2: number; + } + + let a = new A(); + a.num1 = 24; + a.num2 = 23.6; + + let buf = Buffer.from(a.serialize()); + + expect(buf.readUInt16BE(0)).to.equal(12); + expect(buf.readUInt16BE(2)).to.equal(236); + + let a2 = A.deserialize(buf); + expect(a2.num1).to.equal(24); + expect(a2.num2).to.equal(23.6); + }); + it('@Field() accept write and read functions', async () => { + class A extends BitstreamElement { + @Field(16,{number: {format: 'unsigned'},transformers: {write:(v)=>v/2, read:(v)=>v*2}}) num1: number; + @Field(16,{number: {format: 'unsigned'},transformers: {write:(v)=>v*10, read:(v)=>v/10}}) num2: number; + } + + let buf = Buffer.from('000E0401','hex'); + + let a = A.deserialize(buf); + expect(a.num1).to.equal(28); + expect(a.num2).to.equal(102.5); + + buf = Buffer.from(a.serialize()); + + expect(buf.readUInt16BE(0)).to.equal(14); + expect(buf.readUInt16BE(2)).to.equal(1025); + }); + it('reads and writes booleans', () => { + class CustomElement extends BitstreamElement { + @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => !v, write: v => !v}}) a : boolean; + @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => !v, write: v => !v}}) b : boolean; + @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => v != undefined?!v:undefined, write: v => !v}}) c : boolean; + @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => !v, write: v => !v}}) d : boolean; + } + + let element = CustomElement.deserialize(Buffer.from([ 0, 1, 2, 0 ])); + + expect(element.a).to.equal(true); + expect(element.b).to.equal(false); + expect(element.c).to.be.undefined; + expect(element.d).to.equal(true); + + let buffer = element.serialize(); + + expect(Array.from(buffer)).to.eql([ 0, 1, 1, 0 ]); + + }); + it('reads and writes numbers', () => { + class CustomElement extends BitstreamElement { + @Field(8,{number: {format: 'unsigned'},transformers:{read:(v)=>v*2,write:(v)=>v/2}}) a : number; + @Field(16,{number: {format: 'unsigned'}, transformers:{read:(v)=>v*10,write:(v)=>v/10}}) b : number; + @Field(32,{number: {format: 'unsigned'},transformers:{read:(v)=>v*8,write:(v)=>v/8}}) c : number; + @Field(8,{number: {format: 'signed'},transformers:{read:(v)=>v*2,write:(v)=>v/2}}) d : number; + @Field(16,{number: {format: 'signed'}, transformers:{read:(v)=>v*10,write:(v)=>v/10}}) e : number; + @Field(32,{number: {format: 'signed'},transformers:{read:(v)=>v*8,write:(v)=>v/8}}) f : number; + @Field(32,{number: {format: 'float'},transformers:{read:(v)=>v*8,write:(v)=>v/8}}) g : number; + } + + const buf = new DataView(new ArrayBuffer(18)); + buf.setUint8(0, 126); + buf.setUint16(1, 1201); + buf.setUint32(3, 1280000001); + buf.setInt8(7, -127); + buf.setInt16(8, -1200); + buf.setInt32(10, -1280000001); + buf.setFloat32(14, 0.5); + + let element = CustomElement.deserialize(Buffer.from(buf.buffer)); + + expect(element.a).to.equal(126*2); + expect(element.b).to.equal(1201*10); + expect(element.c).to.equal(1280000001*8); + expect(element.d).to.equal(-127*2); + expect(element.e).to.equal(-1200*10); + expect(element.f).to.equal(-1280000001*8); + expect(element.g).to.equal(0.5*8); + + let buffer = element.serialize(); + + expect(Array.from(buffer)).to.eql(Array.from(Buffer.from(buf.buffer))); + }); + it('reads integers and trasform to float', () => { + class CustomElement extends BitstreamElement { + @Field(16, { number: { format: 'unsigned' }, transformers: {read:(v)=>v/10}}) a : number; + @Field(32, { number: { format: 'signed' }, transformers: {read:(v)=>v/100}}) b : number; + @Field(32, { number: { format: 'float' }, transformers: {read:(v)=>v/10}}) c : number; + } + + let element = CustomElement.deserialize(Buffer.from([ + 0x04, 0x01, + 0xc9, 0x56, 0x00, 0x00, + 0xC3, 0xDA, 0x00, 0x00 + ])); + + expect(element.a).to.equal(102.5); + expect(element.b).to.equal(-9171107.84); + expect(element.c).to.equal(-43.6); + }); + it('writes floats and trasform to integer', () => { + class CustomElement extends BitstreamElement { + @Field(16, { number: { format: 'unsigned' }, transformers: {write:(v)=>v*10}}) a : number; + @Field(32, { number: { format: 'signed' }, transformers: {write:(v)=>v*100}}) b : number; + @Field(32, { number: { format: 'float' }, transformers: {write:(v)=>v*10}}) c : number; + } + + let buf = new CustomElement().with({ a: 102.5, b:-9171107.84, c: -43.6 }).serialize(); + expect(Array.from(buf)).to.eql([ + 0x04, 0x01, + 0xc9, 0x56, 0x00, 0x00, + 0xC3, 0xDA, 0x00, 0x00 + ]); + }); + it('reads and writes string', async () => { + class CustomElement extends BitstreamElement { + @Field(4) a; + @Field(4) b; + @Field(5,{transformers:{read: v=>v+" world",write: v=>v.split(' ')[0]}}) c : string; + } + + let bitstream = new BitstreamReader(); + bitstream.addBuffer(Buffer.from([ 0b11010110 ])); + bitstream.addBuffer(Buffer.from('hello', 'utf-8')); + + let element = await CustomElement.readBlocking(bitstream); + + expect(element.a).to.equal(0b1101); + expect(element.b).to.equal(0b0110); + expect(element.c).to.equal('hello world'); + + let buf = element.serialize(); + + expect(Array.from(buf)).to.eql([ 0b11010110, 104,101,108,108,111 ]); + }); + describe(' Array ', () => { + it('of numbers (dynamic)', async () => { + class CustomElement extends BitstreamElement { + @Field(8) before; + @Field(0, { array: { type: Number, countFieldLength: 8, elementLength: 10 }, transformers:{ read: (x) => x >> 1, write: (x) => x << 1} }) items : number[]; + @Field(2) after; + } + + let element = await CustomElement.deserialize(Buffer.from([ + 123, 3, 0b10011001, 0b10100110, 0b01001011, 0b00111010 + ])); + + expect(element.before).to.equal(123); + expect(element.items.length).to.equal(3); + expect(element.items[0]).to.equal(0b0100110011); + expect(element.items[1]).to.equal(0b0100110010); + expect(element.items[2]).to.equal(0b0101100111); + + let buf = element.serialize(); + expect(Array.from(buf)).to.eql([ 123, 3, 0b10011001, 0b10100110, 0b01001011, 0b00111010 ]); + + }); + + it('of elements (dynamic)', async () => { + class ItemElement extends BitstreamElement { + @Field(16,{transformers: { read:(v)=>v/10, write:(v)=>v*10}}) a; + @Field(8,{transformers: { read:(v)=>v*2, write:(v)=>v/2}}) b; + } + class CustomElement extends BitstreamElement { + @Field(8) before; + @Field(0, { array: { type: ItemElement, countFieldLength: 8 } }) items : ItemElement[]; + @Field(8) afterwards; + } + + let bitstream = new BitstreamReader(); + bitstream.addBuffer(Buffer.from([ 123, 3, 0, 1, 2, 0, 11, 12, 0, 21, 22, 123 ])); + + let element = await CustomElement.readBlocking(bitstream); + + expect(element.before).to.equal(123); + expect(element.items.length).to.equal(3); + + expect(element.items[0].a).to.equal(0.1); + expect(element.items[0].b).to.equal(4); + expect(element.items[1].a).to.equal(1.1); + expect(element.items[1].b).to.equal(24); + expect(element.items[2].a).to.equal(2.1); + expect(element.items[2].b).to.equal(44); + expect(element.afterwards).to.equal(123); + + let nbuffer = element.serialize(); + expect(Array.from(nbuffer)).to.eql([ 123, 3, 0, 1, 2, 0, 11, 12, 0, 21, 22, 123 ]); + + }); + it('of numbers', async () => { + class CustomElement extends BitstreamElement { + @Field(4) a; + @Field(4) b; + @Field(0, { array: { type: Number, count: 3, elementLength: 8 }, transformers:{ read: (x) => x / 10, write: (x) => x * 10} }) items : number[]; + } + + let bitstream = new BitstreamReader(); + bitstream.addBuffer(Buffer.from([ 0b11010110 ])); + bitstream.addBuffer(Buffer.from([ 10, 11, 12 ])); + + let element = await CustomElement.readBlocking(bitstream); + + expect(element.a).to.equal(0b1101); + expect(element.b).to.equal(0b0110); + expect(element.items.length).to.equal(3) + expect(element.items[0]).to.equal(1.0); + expect(element.items[1]).to.equal(1.1); + expect(element.items[2]).to.equal(1.2); + + const nbuffer = element.serialize(); + expect(Array.from(nbuffer)).to.eql([ 0b11010110, 10, 11, 12 ]); + }); + it('of elements', async () => { + + class CustomElementItem extends BitstreamElement { + @Field(5) stringField : string; + } + class CustomElement extends BitstreamElement { + @Field(4) a; + @Field(4) b; + @Field(0, { array: { type: CustomElementItem, count: 3 }, transformers:{ read: (x) => {return { stringField:x.stringField + ':yum'}}, write: (x) => {return {stringField:x.stringField.split(':')[0]}}} }) items : CustomElementItem[]; + } + + let bitstream = new BitstreamReader(); + bitstream.addBuffer(Buffer.from([ 0b11010110 ])); + bitstream.addBuffer(Buffer.from('alpha betagamma')); + + let element = await CustomElement.readBlocking(bitstream); + + expect(element.a).to.equal(0b1101); + expect(element.b).to.equal(0b0110); + expect(element.items.length).to.equal(3) + expect(element.items[0].stringField).to.equal('alpha:yum'); + expect(element.items[1].stringField).to.equal(' beta:yum'); + expect(element.items[2].stringField).to.equal('gamma:yum'); + + const nbuffer = element.serialize(); + expect(Array.from(nbuffer)).to.eql([ 0b11010110].concat(Array.from(Buffer.from('alpha betagamma')))); + }); + it('of elements with transformers', async () => { + + class CustomElementItem extends BitstreamElement { + @Field(6,{transformers:{read: v=>v+" world", write: v=>v.split(' ')[0]}}) stringField : string; + } + class CustomElement extends BitstreamElement { + @Field(4) a; + @Field(4) b; + @Field(0, { array: { type: CustomElementItem, count: 3 }}) items : CustomElementItem[]; + } + + let bitstream = new BitstreamReader(); + bitstream.addBuffer(Buffer.from([ 0b11010110 ])); + bitstream.addBuffer(Buffer.from('_hello___byethanks')); + + let element = await CustomElement.readBlocking(bitstream); + + expect(element.a).to.equal(0b1101); + expect(element.b).to.equal(0b0110); + expect(element.items.length).to.equal(3) + expect(element.items[0].stringField).to.equal('_hello world'); + expect(element.items[1].stringField).to.equal('___bye world'); + expect(element.items[2].stringField).to.equal('thanks world'); + + const nbuffer = element.serialize(); + expect(Array.from(nbuffer)).to.eql([ 0b11010110 ].concat(Array.from(Buffer.from('_hello___byethanks')))); + }); + }); + + it('reads and writes buffers', async () => { + class CustomElement extends BitstreamElement { + @Field(15*8,{transformers:{write: v=>v.reverse(), read: v=>v.reverse()}}) c : Uint8Array; + @Field(15*8,{transformers:{write: v=>v.reverse(), read: v=>v.reverse()}}) d : Buffer; + } + + let bitstream = new BitstreamReader(); + bitstream.addBuffer(Buffer.from('123456789012345', 'utf-8')); + bitstream.addBuffer(Buffer.from('ABCDEFGHILMNOPQ', 'utf-8')); + + let element = await CustomElement.readBlocking(bitstream); + + expect(element.c.toString()).to.equal(Uint8Array.from(Buffer.from('543210987654321')).toString()); + expect(element.d.toString()).to.equal(Buffer.from('QPONMLIHGFEDCBA').toString()); + + let nbuffer = element.serialize(); + expect(nbuffer.subarray(0, 15).toString()).to.equal(Uint8Array.from(Buffer.from('123456789012345')).toString()); + expect(nbuffer.subarray(15, 30).toString()).to.equal(Uint8Array.from(Buffer.from('ABCDEFGHILMNOPQ')).toString()); + + }); + it('reads and writes elemets', async () => { + class CustomInnerElement extends BitstreamElement { + @Field(8) c : number; + } + class CustomElement extends BitstreamElement { + @Field(0,{transformers:{write: v=>{return{ c:v.c*10}}, read: v=> {return {c:v.c/10}}}}) d : CustomInnerElement; + } + + let element = await CustomElement.deserialize(Buffer.from(['128'])); + + expect(element.d.c).to.equal(12.8); + + let nbuffer = element.serialize(); + + expect(Array.from(nbuffer)).to.eql([128]); + }); + it('reads and writes elemets with transformes', async () => { + class CustomInnerElement extends BitstreamElement { + @Field(8,{transformers:{write: v=>v*10, read: v=> v / 10}}) c : number; + } + class CustomElement extends BitstreamElement { + @Field(0) d : CustomInnerElement; + } + + let element = CustomElement.deserialize(Buffer.from(['128'])); + + expect(element.d.c).to.equal(12.8); + + let nbuffer = element.serialize(); + + expect(Array.from(nbuffer)).to.eql([128]); + }); + }); +}); + diff --git a/src/elements/element.ts b/src/elements/element.ts index f82822f..b0abe4e 100644 --- a/src/elements/element.ts +++ b/src/elements/element.ts @@ -779,8 +779,9 @@ export class BitstreamElement { } } - if (!element.options.isIgnored) + if (!element.options.isIgnored){ instance[element.name] = readValue; + } instance.readFields.push(element.name); instance.bitsRead += (bitstream.offset - startOffset); @@ -1258,6 +1259,7 @@ export class BitstreamElement { delete element.savedConstructorParams; if (!options?.elementBeingVariated) element.onParseFinished(); + return > element; } diff --git a/src/elements/field-options.ts b/src/elements/field-options.ts index 5042850..58d5211 100644 --- a/src/elements/field-options.ts +++ b/src/elements/field-options.ts @@ -3,6 +3,7 @@ import { ArrayOptions } from "./array-options"; import { BufferOptions } from "./buffer-options"; import { Serializer } from "./serializer"; import { ValueDeterminant } from "./value-determinant"; +import { ValueTransformers } from "./value-transformer"; import { VariantDefinition } from "./variant-definition"; import { NumberOptions } from "./number-options"; import { BooleanOptions } from "./boolean-options"; @@ -159,4 +160,10 @@ export interface FieldOptions { * parsed. */ initializer?: (instance: any, parentElement: any) => void; + + /** + * Transformes to call on parsed values. + * This is called after value parser on read and before value serializer on write + */ + transformers?: ValueTransformers; } diff --git a/src/elements/number-serializer.ts b/src/elements/number-serializer.ts index 7878597..8b40fd4 100644 --- a/src/elements/number-serializer.ts +++ b/src/elements/number-serializer.ts @@ -22,14 +22,22 @@ export class NumberSerializer implements Serializer { yield { remaining: length, contextHint: () => summarizeField(field) }; let format = field.options?.number?.format ?? 'unsigned'; + let numericValue; if (format === 'unsigned') - return reader.readSync(length); + numericValue = reader.readSync(length); else if (format === 'signed') - return reader.readSignedSync(length); + numericValue = reader.readSignedSync(length); else if (format === 'float') - return reader.readFloatSync(length); + numericValue = reader.readFloatSync(length); else throw new TypeError(`Unsupported number format '${format}'`); + + if ( field?.options?.transformers?.read ){ + numericValue = field.options.transformers.read(numericValue,field, parent); + } + + return numericValue; + } write(writer: BitstreamWriter, type : any, instance: any, field: FieldDefinition, value: any) { @@ -45,6 +53,10 @@ export class NumberSerializer implements Serializer { let format = field.options?.number?.format ?? 'unsigned'; + if ( field?.options?.transformers?.write ){ + value = field.options.transformers.write(value,field, instance); + } + if (format === 'unsigned') writer.write(length, value); else if (format === 'signed') diff --git a/src/elements/string-serializer.ts b/src/elements/string-serializer.ts index 2c49404..65381e1 100644 --- a/src/elements/string-serializer.ts +++ b/src/elements/string-serializer.ts @@ -15,8 +15,14 @@ export class StringSerializer implements Serializer { if (!reader.isAvailable(length*8)) yield { remaining: length*8, contextHint: () => summarizeField(field) }; - - return reader.readStringSync(length, field.options.string); + + let value = reader.readStringSync(length, field.options.string); + + if ( field?.options?.transformers?.read ){ + value = field.options.transformers.read(value,field, parent); + } + + return value; } write(writer : BitstreamWriter, type : any, parent : BitstreamElement, field : FieldDefinition, value : any) { @@ -27,6 +33,10 @@ export class StringSerializer implements Serializer { throw new Error(`Failed to resolve length of string via 'length' determinant: ${e.message}`); } + if ( field?.options?.transformers?.write ){ + value = field.options.transformers.write(value,field, parent); + } + writer.writeString(length, `${value}`, field?.options?.string?.encoding || 'utf-8'); } } diff --git a/src/elements/structure-serializer.ts b/src/elements/structure-serializer.ts index 9de1cd9..2c50d22 100644 --- a/src/elements/structure-serializer.ts +++ b/src/elements/structure-serializer.ts @@ -14,14 +14,21 @@ export class StructureSerializer implements Serializer { let result = g.next(); if (result.done === false) yield result.value; - else + else { + if ( field?.options?.transformers?.read ){ + Object.assign(result.value, field.options.transformers.read(result.value,field, parent)); + } return result.value; + } } } write(writer: BitstreamWriter, type : any, instance: any, field: FieldDefinition, value: BitstreamElement) { if (!value) throw new Error(`Cannot write ${field.type.name}#${String(field.name)}: Value is null/undefined`); + if ( field?.options?.transformers?.write ){ + Object.assign(value, field.options.transformers.write(value,field, instance)); + } value.write(writer, { skip: field.options?.skip, context: instance?.context }); } } diff --git a/src/elements/value-transformer.ts b/src/elements/value-transformer.ts new file mode 100644 index 0000000..052f71b --- /dev/null +++ b/src/elements/value-transformer.ts @@ -0,0 +1,8 @@ +import { FieldDefinition } from "./field-definition"; + +/** + * Is a function that dynamically transform value in read and write + * determines the value based on the current context. + */ + export type TrasfromableType = T extends Array ? U : T; + export type ValueTransformers = {read?:(value: TrasfromableType, f : FieldDefinition, instance? : TrasfromableType) => V, write?:(value: TrasfromableType, f : FieldDefinition, instance? : TrasfromableType) => V};