From 3f9c7f38b7defc3c437be6f2613d9bb73c4915c9 Mon Sep 17 00:00:00 2001 From: dianlight Date: Wed, 23 Oct 2024 01:21:19 +0200 Subject: [PATCH 1/5] Support for Transformers --- CHANGELOG.md | 1 + README.md | 14 ++++ src/elements/element.test.ts | 114 ++++++++++++++++++++++++++++++ src/elements/element.ts | 11 ++- src/elements/field-options.ts | 7 ++ src/elements/value-transformer.ts | 7 ++ 6 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/elements/value-transformer.ts 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/element.test.ts b/src/elements/element.test.ts index 42893db..657d67a 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,6 +148,35 @@ describe('BitstreamElement', it => { expect(b.field1).to.equal(1); expect(b.field2).to.equal(2); }); + + describe(': Transformers', it => { + it('@Field() accepts read transformer', 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 transformer', 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() accepts single options when length is inferred', async () => { class CustomElement2 extends BitstreamElement { @@ -509,6 +540,37 @@ describe('BitstreamElement', it => { 0,0,0,0 ]); }); + it('reads integer trasform to floats ', () => { + 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 trasform to ineteger', () => { + 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('throws with an invalid format while reading', () => { class CustomElement extends BitstreamElement { @Field(32, { number: { format: 'invalid' }}) a : number; @@ -711,6 +773,32 @@ describe('BitstreamElement', it => { let buffer = new CustomElement().with({ a: true, b: false, c: undefined, d: false }).serialize(); expect(Array.from(buffer)).to.eql([ 1, 0, 99, 0 ]); }); + it('behaves correctly with transformer', () => { + class CustomElement extends BitstreamElement { + @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => !v}}) a : boolean; + @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => !v}}) b : boolean; + @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => v != undefined?!v:undefined}}) c : boolean; + @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => !v}}) d : boolean; + } + + let element = CustomElement.deserialize(Buffer.from([ 0, 1, 2, 0 ])); + + expect(element.a).to.equal(!false); + expect(element.b).to.equal(!true); + expect(element.c).to.be.undefined; + expect(element.d).to.equal(!false); + }); + it('respects the transformet value while writing', () => { + class CustomElement extends BitstreamElement { + @Field(8, { boolean: { undefined: 99 },transformers:{write: v => !v}}) a : boolean; + @Field(8, { boolean: { undefined: 99 },transformers:{write: v => !v}}) b : boolean; + @Field(8, { boolean: { undefined: 99 },transformers:{write: v => !v}}) c : boolean; + @Field(8, { boolean: { undefined: 99 },transformers:{write: v => !v}}) d : boolean; + } + + let buffer = new CustomElement().with({ a: !true, b: !false, c: undefined, d: !false }).serialize(); + expect(Array.from(buffer)).to.eql([ 1, 0, 1, 0 ]); + }); }); describe(': Byte Arrays', it => { it('understands Buffer when length is a multiple of 8', async () => { @@ -1008,6 +1096,32 @@ describe('BitstreamElement', it => { expect(buf.toString('utf-8')).to.equal('hello'); }); + it('are read and transformed correctly', async () => { + class CustomElement extends BitstreamElement { + @Field(4) a; + @Field(4) b; + @Field(5,{transformers:{read: v=>v+" world"}}) 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'); + }); + it('are written and trasformed correctly', async () => { + class CustomElement extends BitstreamElement { + @Field(5,{transformers:{write: v=>v.split(' ')[0]}}) c : string; + } + + let buf = Buffer.from(new CustomElement().with({ c: 'hello world' }).serialize()); + + expect(buf.toString('utf-8')).to.equal('hello'); + }); it('are written correctly (utf16le)', async () => { class CustomElement extends BitstreamElement { @Field(10, { string: { encoding: 'utf16le' }}) c : string; diff --git a/src/elements/element.ts b/src/elements/element.ts index f82822f..a86a503 100644 --- a/src/elements/element.ts +++ b/src/elements/element.ts @@ -309,6 +309,10 @@ export class BitstreamElement { } } + if (field.options.transformers?.write){ + writtenValue = field.options.transformers.write(writtenValue,this,field); + } + try { field.options.serializer.write(writer, field.type, this, field, writtenValue); } catch (e) { @@ -779,8 +783,12 @@ export class BitstreamElement { } } - if (!element.options.isIgnored) + if (!element.options.isIgnored){ + if(element.options.transformers?.read){ + readValue = element.options.transformers.read(readValue,this,element); + } instance[element.name] = readValue; + } instance.readFields.push(element.name); instance.bitsRead += (bitstream.offset - startOffset); @@ -1258,6 +1266,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/value-transformer.ts b/src/elements/value-transformer.ts new file mode 100644 index 0000000..014bd0c --- /dev/null +++ b/src/elements/value-transformer.ts @@ -0,0 +1,7 @@ +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 ValueTransformers = {read?:(value: V,instance : T, f : FieldDefinition) => V, write?:(value:V,instance : T, f : FieldDefinition) => V}; From 539232f33b84d013f88989bba7610941c0fe3693 Mon Sep 17 00:00:00 2001 From: dianlight Date: Mon, 4 Nov 2024 00:11:15 +0100 Subject: [PATCH 2/5] Transformer serialize, deserialize Test --- src/elements/element.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/elements/element.test.ts b/src/elements/element.test.ts index 657d67a..b3f09e4 100644 --- a/src/elements/element.test.ts +++ b/src/elements/element.test.ts @@ -176,6 +176,25 @@ describe('BitstreamElement', it => { expect(buf.readUInt8(0)).to.equal(123); expect(buf.readUInt8(1)).to.equal(124); }); + it('@Field() accept read and write transformer', 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() accepts single options when length is inferred', async () => { From 1594aac093dafb486b989e3544ab63ca33df5416 Mon Sep 17 00:00:00 2001 From: dianlight Date: Mon, 4 Nov 2024 00:15:58 +0100 Subject: [PATCH 3/5] Add trasformers test from write to read --- src/elements/element.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/elements/element.test.ts b/src/elements/element.test.ts index b3f09e4..b0a4c2d 100644 --- a/src/elements/element.test.ts +++ b/src/elements/element.test.ts @@ -195,6 +195,23 @@ describe('BitstreamElement', it => { expect(a2.num1).to.equal(24); expect(a2.num2).to.equal(23.6); }); + it('@Field() accept write and read transformer', 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('@Field() accepts single options when length is inferred', async () => { From ef3650260a1547952da500c89b30221857d6dc3f Mon Sep 17 00:00:00 2001 From: dianlight Date: Mon, 4 Nov 2024 12:08:26 +0100 Subject: [PATCH 4/5] Correct use of transformers in Array of Elements --- src/elements/element.test.ts | 31 +++++++++++++++++++++++++++++++ src/elements/element.ts | 4 ++++ 2 files changed, 35 insertions(+) diff --git a/src/elements/element.test.ts b/src/elements/element.test.ts index b0a4c2d..c62830a 100644 --- a/src/elements/element.test.ts +++ b/src/elements/element.test.ts @@ -1593,6 +1593,37 @@ describe('BitstreamElement', it => { expect(element.items[2].b).to.equal(22); expect(element.afterwards).to.equal(123); }); + it('should correctly parse and serialize elements with transformer', async () => { + class ItemElement extends BitstreamElement { + @Field(8,{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, 1, 2, 11, 12, 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, 1, 2, 11, 12, 21, 22, 123 ]); + + }); it('should understand hasMore discriminant', async () => { class CustomItem extends BitstreamElement { @Field(8) byte; diff --git a/src/elements/element.ts b/src/elements/element.ts index a86a503..a4ed712 100644 --- a/src/elements/element.ts +++ b/src/elements/element.ts @@ -865,6 +865,10 @@ export class BitstreamElement { let writtenValue = this[element.name]; + if (element.options.transformers?.write) { + writtenValue = element.options.transformers.write(writtenValue, this, element); + } + if (element.options.writtenValue) { if (typeof element.options.writtenValue === 'function') { writtenValue = element.options.writtenValue(this, element); From 1ddee10adaacb3feeca25712569b4600a656babd Mon Sep 17 00:00:00 2001 From: dianlight Date: Mon, 4 Nov 2024 23:51:43 +0100 Subject: [PATCH 5/5] New version of transformes with tests --- src/elements/array-serializer.ts | 14 + src/elements/boolean-serializer.ts | 21 +- src/elements/buffer-serializer.ts | 10 + src/elements/element.test.ts | 537 ++++++++++++++++++--------- src/elements/element.ts | 11 - src/elements/number-serializer.ts | 18 +- src/elements/string-serializer.ts | 14 +- src/elements/structure-serializer.ts | 9 +- src/elements/value-transformer.ts | 3 +- 9 files changed, 435 insertions(+), 202 deletions(-) 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 c62830a..e1d4045 100644 --- a/src/elements/element.test.ts +++ b/src/elements/element.test.ts @@ -149,71 +149,6 @@ describe('BitstreamElement', it => { expect(b.field2).to.equal(2); }); - describe(': Transformers', it => { - it('@Field() accepts read transformer', 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 transformer', 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 transformer', 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 transformer', 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('@Field() accepts single options when length is inferred', async () => { class CustomElement2 extends BitstreamElement { @Field(8) byte2: number; @@ -576,37 +511,6 @@ describe('BitstreamElement', it => { 0,0,0,0 ]); }); - it('reads integer trasform to floats ', () => { - 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 trasform to ineteger', () => { - 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('throws with an invalid format while reading', () => { class CustomElement extends BitstreamElement { @Field(32, { number: { format: 'invalid' }}) a : number; @@ -809,32 +713,6 @@ describe('BitstreamElement', it => { let buffer = new CustomElement().with({ a: true, b: false, c: undefined, d: false }).serialize(); expect(Array.from(buffer)).to.eql([ 1, 0, 99, 0 ]); }); - it('behaves correctly with transformer', () => { - class CustomElement extends BitstreamElement { - @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => !v}}) a : boolean; - @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => !v}}) b : boolean; - @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => v != undefined?!v:undefined}}) c : boolean; - @Field(8, { boolean: { mode: 'undefined' },transformers:{read: v => !v}}) d : boolean; - } - - let element = CustomElement.deserialize(Buffer.from([ 0, 1, 2, 0 ])); - - expect(element.a).to.equal(!false); - expect(element.b).to.equal(!true); - expect(element.c).to.be.undefined; - expect(element.d).to.equal(!false); - }); - it('respects the transformet value while writing', () => { - class CustomElement extends BitstreamElement { - @Field(8, { boolean: { undefined: 99 },transformers:{write: v => !v}}) a : boolean; - @Field(8, { boolean: { undefined: 99 },transformers:{write: v => !v}}) b : boolean; - @Field(8, { boolean: { undefined: 99 },transformers:{write: v => !v}}) c : boolean; - @Field(8, { boolean: { undefined: 99 },transformers:{write: v => !v}}) d : boolean; - } - - let buffer = new CustomElement().with({ a: !true, b: !false, c: undefined, d: !false }).serialize(); - expect(Array.from(buffer)).to.eql([ 1, 0, 1, 0 ]); - }); }); describe(': Byte Arrays', it => { it('understands Buffer when length is a multiple of 8', async () => { @@ -1132,32 +1010,6 @@ describe('BitstreamElement', it => { expect(buf.toString('utf-8')).to.equal('hello'); }); - it('are read and transformed correctly', async () => { - class CustomElement extends BitstreamElement { - @Field(4) a; - @Field(4) b; - @Field(5,{transformers:{read: v=>v+" world"}}) 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'); - }); - it('are written and trasformed correctly', async () => { - class CustomElement extends BitstreamElement { - @Field(5,{transformers:{write: v=>v.split(' ')[0]}}) c : string; - } - - let buf = Buffer.from(new CustomElement().with({ c: 'hello world' }).serialize()); - - expect(buf.toString('utf-8')).to.equal('hello'); - }); it('are written correctly (utf16le)', async () => { class CustomElement extends BitstreamElement { @Field(10, { string: { encoding: 'utf16le' }}) c : string; @@ -1593,37 +1445,6 @@ describe('BitstreamElement', it => { expect(element.items[2].b).to.equal(22); expect(element.afterwards).to.equal(123); }); - it('should correctly parse and serialize elements with transformer', async () => { - class ItemElement extends BitstreamElement { - @Field(8,{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, 1, 2, 11, 12, 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, 1, 2, 11, 12, 21, 22, 123 ]); - - }); it('should understand hasMore discriminant', async () => { class CustomItem extends BitstreamElement { @Field(8) byte; @@ -2538,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 a4ed712..b0abe4e 100644 --- a/src/elements/element.ts +++ b/src/elements/element.ts @@ -309,10 +309,6 @@ export class BitstreamElement { } } - if (field.options.transformers?.write){ - writtenValue = field.options.transformers.write(writtenValue,this,field); - } - try { field.options.serializer.write(writer, field.type, this, field, writtenValue); } catch (e) { @@ -784,9 +780,6 @@ export class BitstreamElement { } if (!element.options.isIgnored){ - if(element.options.transformers?.read){ - readValue = element.options.transformers.read(readValue,this,element); - } instance[element.name] = readValue; } instance.readFields.push(element.name); @@ -865,10 +858,6 @@ export class BitstreamElement { let writtenValue = this[element.name]; - if (element.options.transformers?.write) { - writtenValue = element.options.transformers.write(writtenValue, this, element); - } - if (element.options.writtenValue) { if (typeof element.options.writtenValue === 'function') { writtenValue = element.options.writtenValue(this, element); 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 index 014bd0c..052f71b 100644 --- a/src/elements/value-transformer.ts +++ b/src/elements/value-transformer.ts @@ -4,4 +4,5 @@ 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 ValueTransformers = {read?:(value: V,instance : T, f : FieldDefinition) => V, write?:(value:V,instance : T, f : FieldDefinition) => V}; + 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};