├── TODO.txt ├── .gitignore ├── src ├── Prototype.js ├── utility │ ├── getParent.js │ ├── StreamBufferWriter.js │ ├── StreamBufferReader.js │ ├── BufferReader.js │ └── BufferWriter.js ├── types │ ├── Type.js │ ├── BigIntType.js │ ├── DateType.js │ └── RegexType.js ├── functions │ ├── flattenValidation.js │ ├── definition.js │ ├── serialize.js │ ├── clone.js │ ├── cast.js │ └── validate.js ├── constants.js └── Tbjson.js ├── rollup.config.js ├── test ├── serialize.js ├── plain.js ├── clone.js ├── unbuild.js ├── selection.js ├── definition.js ├── nestedDefinition.js ├── prototype.js ├── typedSelection.js ├── inheritance.js ├── typesCast.js ├── plainInTyped.js ├── nulls.js ├── flattenValidation.js ├── prototypeCast.js ├── types.js ├── typed.js ├── variableDefs.js ├── nested.js ├── typedArray.js ├── nullable.js ├── cast.js ├── index.js ├── validate.js └── strings.js ├── package.json └── README.md /TODO.txt: -------------------------------------------------------------------------------- 1 | - go through TODOs 2 | - check max object value (uint16 currently) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /coverage 3 | 4 | .DS_Store 5 | .env 6 | .idea 7 | .vscode 8 | *.lnk 9 | 10 | package-lock.json 11 | npm-debug.log -------------------------------------------------------------------------------- /src/Prototype.js: -------------------------------------------------------------------------------- 1 | export default class Prototype { 2 | constructor(definition, prototype = null, parentCode = null, noInherit = false) { 3 | this.definition = definition; 4 | this.prototype = prototype; 5 | this.parentCode = parentCode; 6 | this.noInherit = noInherit; 7 | } 8 | } -------------------------------------------------------------------------------- /src/utility/getParent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Return the parent of a prototype. 3 | * 4 | * @param { function } prototype - prototype to check for parent of 5 | */ 6 | export default function getParent(prototype) { 7 | let parent = prototype ? Object.getPrototypeOf(prototype) : null; 8 | return (parent && parent.name) ? parent : null; 9 | } -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel'; 2 | 3 | export default { 4 | external: [ 5 | 'fs' 6 | ], 7 | input: 'src/Tbjson.js', 8 | output: { 9 | exports: 'auto', 10 | file: 'lib/index.js', 11 | format: 'cjs' 12 | }, 13 | plugins: [ 14 | babel({ 15 | babelHelpers: 'bundled', 16 | exclude: 'node_modules/**' 17 | }) 18 | ] 19 | }; -------------------------------------------------------------------------------- /src/types/Type.js: -------------------------------------------------------------------------------- 1 | export default class Type { 2 | 3 | constructor(ref, size) { 4 | 5 | // a type reference (name) always starts with @ 6 | if (typeof ref == 'string') { 7 | ref = ref[0] == '@' ? ref : '@' + ref; 8 | } else { 9 | ref = null; 10 | } 11 | 12 | this.ref = ref; 13 | this.size = size || 0; 14 | } 15 | 16 | serialize() {} 17 | deserialize() {} 18 | } -------------------------------------------------------------------------------- /test/serialize.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | class A { 4 | string = 'test'; 5 | num = 33; 6 | hidden = true 7 | } 8 | A.tbjson = { 9 | definition: { 10 | string: Tbjson.TYPES.STRING, 11 | num: Tbjson.TYPES.FLOAT64 12 | } 13 | }; 14 | 15 | let x = new A(); 16 | delete x.hidden; 17 | 18 | return [ 19 | stringify(x), 20 | stringify(Tbjson.serialize(new A())) 21 | ]; 22 | } -------------------------------------------------------------------------------- /test/plain.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | let x = { 6 | int: 999, 7 | string: 'string', 8 | array: [10, 10.0001, true, 'apple'], 9 | object: { 10 | float: 10.994, 11 | bool: false 12 | }, 13 | null: null, 14 | undefined: undefined 15 | }; 16 | 17 | return [ 18 | stringify(x), 19 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 20 | ]; 21 | } -------------------------------------------------------------------------------- /test/clone.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | class A { 4 | string = 44; 5 | } 6 | A.tbjson = { 7 | definition: { 8 | string: Tbjson.TYPES.STRING 9 | }, 10 | unbuild: (obj) => ({ 11 | string: '' + obj.string 12 | }), 13 | build: (obj) => { 14 | obj.string = parseFloat(obj.string) 15 | } 16 | }; 17 | 18 | let x = new A(); 19 | 20 | return [ 21 | stringify(x), 22 | stringify(Tbjson.clone(x)) 23 | ]; 24 | } -------------------------------------------------------------------------------- /src/types/BigIntType.js: -------------------------------------------------------------------------------- 1 | import Type from './Type'; 2 | 3 | export default class BigIntType extends Type { 4 | 5 | constructor(ref) { 6 | super(ref || BigIntType.ref, BigIntType.size); 7 | } 8 | 9 | serialize(bigint) { 10 | 11 | let buffer = Buffer.allocUnsafe(BigIntType.size); 12 | buffer.writeBigInt64BE(bigint); 13 | 14 | return buffer; 15 | } 16 | 17 | deserialize(buffer) { 18 | return buffer.readBigInt64BE(); 19 | } 20 | } 21 | 22 | BigIntType.ref = '@BigInt'; 23 | BigIntType.size = 8; -------------------------------------------------------------------------------- /src/types/DateType.js: -------------------------------------------------------------------------------- 1 | import Type from './Type'; 2 | 3 | export default class DateType extends Type { 4 | 5 | constructor(ref) { 6 | super(ref || DateType.ref, DateType.size); 7 | } 8 | 9 | serialize(date) { 10 | 11 | let buffer = Buffer.allocUnsafe(DateType.size); 12 | buffer.writeBigInt64BE(BigInt(date.getTime())); 13 | 14 | return buffer; 15 | } 16 | 17 | deserialize (buffer) { 18 | return new Date(Number(buffer.readBigInt64BE())); 19 | } 20 | } 21 | 22 | DateType.ref = '@Date'; 23 | DateType.size = 8; -------------------------------------------------------------------------------- /test/unbuild.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class A { 6 | string = 44; 7 | } 8 | A.tbjson = { 9 | definition: { 10 | string: Tbjson.TYPES.STRING 11 | }, 12 | unbuild: (obj) => ({ 13 | string: '' + obj.string 14 | }), 15 | build: (obj) => { 16 | obj.string = parseFloat(obj.string) 17 | } 18 | }; 19 | 20 | let x = new A(); 21 | 22 | return [ 23 | stringify(x), 24 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 25 | ]; 26 | } -------------------------------------------------------------------------------- /test/selection.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | let x = { 6 | int: 999, 7 | string: 'string', 8 | array: [10, 10.0001, true, 'apple'], 9 | object: { 10 | float: 10.994, 11 | bool: false, 12 | subObject: { 13 | string: "string", 14 | bool: true, 15 | float: 11.001 16 | } 17 | }, 18 | null: null, 19 | undefined: undefined 20 | }; 21 | 22 | return [ 23 | stringify(x.object.subObject), 24 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x), ['object', 'subObject'])) 25 | ]; 26 | } -------------------------------------------------------------------------------- /src/types/RegexType.js: -------------------------------------------------------------------------------- 1 | import Type from './Type'; 2 | 3 | export default class RegexType extends Type { 4 | 5 | constructor(ref) { 6 | super(ref || RegexType.ref); 7 | } 8 | 9 | serialize(regex) { 10 | 11 | let string = regex.toString(); 12 | let buffer = Buffer.allocUnsafe(string.length); 13 | 14 | buffer.write(string, 'utf8'); 15 | 16 | return buffer; 17 | } 18 | 19 | deserialize (buffer) { 20 | 21 | let string = buffer.toString('utf8'); 22 | let flagIndex = string.lastIndexOf('/'); 23 | 24 | return new RegExp(string.slice(1, flagIndex), string.slice(flagIndex + 1)); 25 | } 26 | } 27 | 28 | RegexType.ref = '@Regex'; -------------------------------------------------------------------------------- /src/functions/flattenValidation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert a validation result into an array with path selectors and errors. 3 | * 4 | * @param { array } validationResult - validation result to flatten 5 | */ 6 | export default function flattenValidation(validationResult) { 7 | return validationResult[1] > 0 ? flattenObj(validationResult[0]) : []; 8 | } 9 | 10 | /* internal */ 11 | 12 | function flattenObj(obj, errors = [], path = []) { 13 | 14 | // recurse 15 | if (typeof obj == 'object' && obj != null) { 16 | for (let key in obj) { 17 | flattenObj(obj[key], errors, path.concat(key)); 18 | } 19 | 20 | // add to array 21 | } else { 22 | errors.push([path, obj]); 23 | } 24 | 25 | return errors; 26 | } -------------------------------------------------------------------------------- /src/functions/definition.js: -------------------------------------------------------------------------------- 1 | import getParent from '../utility/getParent'; 2 | 3 | /** 4 | * Return the flattened TBJSON definition. For prototypes that have parents. 5 | * 6 | * @param { obj } obj - object to compute definition of 7 | */ 8 | export default function definition(obj) { 9 | if (obj && typeof obj == 'object' && obj.constructor.tbjson && obj.constructor.tbjson.definition) { 10 | 11 | let definition = obj.constructor.tbjson.definition; 12 | 13 | for (let parent = obj.constructor; parent = getParent(parent);) { 14 | if (!parent.tbjson || !parent.tbjson.definition) { break; } 15 | definition = Object.assign({}, parent.tbjson.definition, definition); 16 | } 17 | 18 | return definition; 19 | } 20 | } -------------------------------------------------------------------------------- /test/definition.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | class A { 4 | a = 1; 5 | b = '1'; 6 | c = false; 7 | } 8 | A.tbjson = { 9 | definition: { 10 | a: Tbjson.TYPES.INT32, 11 | b: Tbjson.TYPES.STRING, 12 | c: Tbjson.TYPES.BOOL, 13 | } 14 | }; 15 | 16 | class B extends A { 17 | c = { 18 | d: [] 19 | }; 20 | e = true; 21 | } 22 | B.tbjson = { 23 | definition: { 24 | c: Tbjson.TYPES.OBJECT, 25 | e: Tbjson.TYPES.BOOL 26 | } 27 | }; 28 | 29 | class C extends B { 30 | f = 'f'; 31 | } 32 | C.tbjson = { 33 | definition: { 34 | f: Tbjson.TYPES.STRING 35 | } 36 | }; 37 | 38 | let x = new C(); 39 | 40 | return [ 41 | stringify({ 42 | ...A.tbjson.definition, 43 | ...B.tbjson.definition, 44 | ...C.tbjson.definition, 45 | }), 46 | stringify(Tbjson.definition(x)) 47 | ]; 48 | } -------------------------------------------------------------------------------- /test/nestedDefinition.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class A { 6 | vals = { 7 | a: [ 8 | [8192, 0, 3.14159], 9 | [8192, 0, 3.14159] 10 | ], 11 | b: ['a', true, null], 12 | c: { 13 | 1: { 14 | 2: [1, 2] 15 | } 16 | } 17 | }; 18 | } 19 | A.tbjson = { 20 | definition: { 21 | vals: { 22 | a: [Tbjson.TYPES.ARRAY, [Tbjson.TYPES.UINT32, Tbjson.TYPES.UINT8, Tbjson.TYPES.FLOAT64]], 23 | b: [Tbjson.TYPES.STRING, Tbjson.TYPES.BOOL, [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.FLOAT32]], 24 | c: [Tbjson.TYPES.OBJECT, [Tbjson.TYPES.OBJECT, [Tbjson.TYPES.UINT8, Tbjson.TYPES.UINT8]]] 25 | } 26 | } 27 | }; 28 | 29 | let x = new A(); 30 | 31 | return [ 32 | stringify(x), 33 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 34 | ]; 35 | } -------------------------------------------------------------------------------- /test/prototype.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class A { 6 | uint8 = 255; 7 | float64 = 100000.1234567; 8 | string = 'string'; 9 | } 10 | A.tbjson = { 11 | definition: { 12 | uint8: Tbjson.TYPES.UINT8, 13 | float64: Tbjson.TYPES.FLOAT64, 14 | string: Tbjson.TYPES.STRING 15 | } 16 | }; 17 | 18 | class B { 19 | float32 = 100.12345; 20 | a = new A(); 21 | as = []; 22 | 23 | constructor() { 24 | for (let i = 0; i < 10; ++i) { 25 | this.as.push(new A()); 26 | } 27 | } 28 | } 29 | B.tbjson = { 30 | definition: { 31 | float32: Tbjson.TYPES.FLOAT32, 32 | a: A, 33 | as: [Tbjson.TYPES.ARRAY, A] 34 | } 35 | }; 36 | 37 | let x = { 38 | b: new B() 39 | }; 40 | 41 | return [ 42 | stringify(x), 43 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 44 | ]; 45 | } -------------------------------------------------------------------------------- /test/typedSelection.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class A { 6 | uint8 = 255; 7 | float64 = 100000.1234567; 8 | string = 'string'; 9 | object = { 10 | string: "string", 11 | bool: true, 12 | float: 11.001, 13 | subObject: { 14 | uint8: 255, 15 | string: 'string' 16 | } 17 | }; 18 | } 19 | A.tbjson = { 20 | definition: { 21 | uint8: Tbjson.TYPES.UINT8, 22 | float64: Tbjson.TYPES.FLOAT64, 23 | string: Tbjson.TYPES.STRING, 24 | object: Tbjson.TYPES.OBJECT 25 | } 26 | }; 27 | 28 | let x = { 29 | int: 999, 30 | string: 'string', 31 | array: [10, 10.0001, true, 'apple'], 32 | a: new A(), 33 | null: null 34 | }; 35 | 36 | return [ 37 | stringify(x.a.object.subObject), 38 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x), ['a', 'object', 'subObject'])) 39 | ]; 40 | } -------------------------------------------------------------------------------- /src/utility/StreamBufferWriter.js: -------------------------------------------------------------------------------- 1 | import BufferWriter from './BufferWriter'; 2 | 3 | export default class StreamBufferWriter extends BufferWriter { 4 | 5 | streamIndex = 0; 6 | streamReady = true; 7 | 8 | constructor(stream, size, xFactor, strEncoding) { 9 | super(size, xFactor, strEncoding); 10 | this.stream = stream; 11 | } 12 | 13 | /* internal */ 14 | 15 | flush() { 16 | 17 | this.streamReady = this.stream.write(this.buffer.slice(this.streamIndex, this.offset), () => { 18 | this.streamReady = true; 19 | }); 20 | 21 | if (this.streamReady) { 22 | this.offset = 0; 23 | this.streamIndex = 0; 24 | } else { 25 | this.streamIndex = this.offset; 26 | } 27 | 28 | return this.streamReady; 29 | } 30 | 31 | checkSize(size) { 32 | if (this.offset + size > this.size) { 33 | if (this.streamReady && !this.flush()) { 34 | this.grow(); 35 | } else { 36 | this.grow(); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /test/inheritance.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class A { 6 | x = 10000.666; 7 | y = -9999.999; 8 | z = 1234.5678; 9 | } 10 | 11 | class B extends A { 12 | bool = false; 13 | string = 'string'; 14 | } 15 | 16 | class C extends A { 17 | b = new B(); 18 | } 19 | C.tbjson = { 20 | definition: { 21 | b: B 22 | } 23 | }; 24 | 25 | let x = { 26 | b: new B() 27 | }; 28 | 29 | tbjson.registerPrototype({ 30 | prototype: A, 31 | definition: { 32 | x: Tbjson.TYPES.FLOAT32, 33 | y: Tbjson.TYPES.FLOAT32, 34 | z: Tbjson.TYPES.FLOAT32 35 | } 36 | }); 37 | 38 | tbjson.registerPrototype({ 39 | prototype: B, 40 | definition: { 41 | bool: Tbjson.TYPES.BOOL, 42 | string: Tbjson.TYPES.STRING 43 | } 44 | }); 45 | 46 | tbjson.finalizePrototypes(); 47 | 48 | return [ 49 | stringify(x), 50 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 51 | ]; 52 | } -------------------------------------------------------------------------------- /test/typesCast.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson1 = new Tbjson(); 4 | let tbjson2 = new Tbjson(); 5 | 6 | tbjson1.registerTypes([ 7 | new Tbjson.Types.BigIntType(), 8 | new Tbjson.Types.DateType(), 9 | new Tbjson.Types.RegexType() 10 | ]); 11 | 12 | class A { 13 | bigint = -999_999_999_999_999n; 14 | date = new Date(-1000); 15 | regex = /abcdefg\\/i; 16 | }; 17 | 18 | A.tbjson = { 19 | definition: { 20 | bigint: Tbjson.Types.BigIntType.ref, 21 | date: Tbjson.Types.DateType.ref, 22 | regex: Tbjson.Types.RegexType.ref 23 | } 24 | }; 25 | 26 | let x = new A(); 27 | 28 | return [ 29 | stringify(x, true), 30 | stringify( 31 | Tbjson.cast(tbjson2.parseBuffer(tbjson1.serializeToBuffer(x)), A, { 32 | [Tbjson.Types.BigIntType.ref]: new Tbjson.Types.BigIntType(), 33 | [Tbjson.Types.DateType.ref]: new Tbjson.Types.DateType(), 34 | [Tbjson.Types.RegexType.ref]: new Tbjson.Types.RegexType() 35 | }), 36 | true 37 | ) 38 | ]; 39 | } -------------------------------------------------------------------------------- /test/plainInTyped.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class A { 6 | uint8 = 255; 7 | float64 = 100000.1234567; 8 | string = 'string'; 9 | unknown = { 10 | a: "a", 11 | b: 100, 12 | c: false, 13 | d: [1, 2, 3] 14 | }; 15 | } 16 | A.tbjson = { 17 | definition: { 18 | uint8: Tbjson.TYPES.UINT8, 19 | float64: Tbjson.TYPES.FLOAT64, 20 | string: Tbjson.TYPES.STRING, 21 | unknown: Tbjson.TYPES.OBJECT 22 | } 23 | }; 24 | 25 | class B { 26 | float32 = 100.12345; 27 | a = new A(); 28 | as = []; 29 | 30 | constructor() { 31 | for (let i = 0; i < 10; ++i) { 32 | this.as.push(new A()); 33 | } 34 | } 35 | } 36 | B.tbjson = { 37 | definition: { 38 | float32: Tbjson.TYPES.FLOAT32, 39 | a: A, 40 | as: [Tbjson.TYPES.ARRAY, A] 41 | } 42 | }; 43 | 44 | let x = { 45 | b: new B() 46 | }; 47 | 48 | return [ 49 | stringify(x), 50 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 51 | ]; 52 | } -------------------------------------------------------------------------------- /test/nulls.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson([], [{ 4 | reference: 'A', 5 | definition: { 6 | bool: Tbjson.TYPES.BOOL, 7 | uint8: Tbjson.TYPES.UINT8, 8 | int8: Tbjson.TYPES.INT8, 9 | uint16: Tbjson.TYPES.UINT16, 10 | int16: Tbjson.TYPES.INT16, 11 | uint32: Tbjson.TYPES.UINT32, 12 | int32: Tbjson.TYPES.INT32, 13 | float32: Tbjson.TYPES.FLOAT32, 14 | float64: Tbjson.TYPES.FLOAT64, 15 | string: Tbjson.TYPES.STRING 16 | } 17 | }]); 18 | 19 | class A { 20 | bool = null; 21 | uint8 = null; 22 | int8 = null; 23 | uint16 = null; 24 | int16 = null; 25 | uint32 = null; 26 | int32 = null; 27 | float32 = null; 28 | float64 = null; 29 | string = null; 30 | } 31 | 32 | let x = new A(); 33 | 34 | let res = stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))); 35 | 36 | // set nulls to default 37 | x.bool = false; 38 | x.uint8 = 0; 39 | x.int8 = 0; 40 | x.uint16 = 0; 41 | x.int16 = 0; 42 | x.uint32 = 0; 43 | x.int32 = 0; 44 | x.float32 = 0; 45 | x.float64 = 0; 46 | x.string = ''; 47 | 48 | return [ 49 | stringify(x), 50 | res 51 | ]; 52 | } -------------------------------------------------------------------------------- /test/flattenValidation.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | class C { 4 | validInt32 = 0; 5 | invalidInt32 = 9999999999; 6 | invalidFloat64Array = [0, true, 2, 'true']; 7 | } 8 | C.tbjson = { 9 | definition: { 10 | validInt32: Tbjson.TYPES.INT32, 11 | invalidInt32: Tbjson.TYPES.INT32, 12 | invalidFloat64Array: [Tbjson.TYPES.ARRAY, Tbjson.TYPES.FLOAT64], 13 | } 14 | }; 15 | 16 | class B { 17 | invalidString = 1; 18 | c = new C(); 19 | } 20 | B.tbjson = { 21 | definition: { 22 | invalidString: Tbjson.TYPES.STRING, 23 | c: C 24 | } 25 | }; 26 | 27 | class A { 28 | validBool = true; 29 | invalidBool = 'true'; 30 | b = new B(); 31 | } 32 | 33 | A.tbjson = { 34 | definition: { 35 | validBool: Tbjson.TYPES.BOOL, 36 | invalidBool: Tbjson.TYPES.BOOL, 37 | b: B 38 | } 39 | }; 40 | 41 | let x = new A(); 42 | 43 | return [ 44 | stringify([ 45 | [['invalidBool'], 1], 46 | [['b', 'invalidString'], 10], 47 | [['b', 'c', 'invalidInt32'], 7], 48 | [['b', 'c', 'invalidFloat64Array', '1'], 9], 49 | [['b', 'c', 'invalidFloat64Array', '3'], 9], 50 | ]), 51 | stringify(Tbjson.flattenValidation(Tbjson.validate(x))) 52 | ]; 53 | } -------------------------------------------------------------------------------- /test/prototypeCast.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify, validateTypes) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class Item {} 6 | Item.tbjson = { 7 | definition: {}, 8 | cast: (obj) => obj.a ? A : B 9 | }; 10 | 11 | class A extends Item { 12 | a = 100; 13 | } 14 | A.tbjson = { 15 | definition: { 16 | a: Tbjson.TYPES.INT32 17 | } 18 | }; 19 | 20 | class B extends Item { 21 | b = 1000; 22 | } 23 | B.tbjson = { 24 | definition: { 25 | b: Tbjson.TYPES.INT32 26 | } 27 | }; 28 | 29 | class X { 30 | item = new A(); 31 | itemsArray = [new A(), new B(), new B(), new A()]; 32 | itemsObject = { 33 | a: new A(), 34 | b: new B() 35 | }; 36 | } 37 | X.tbjson = { 38 | definition: { 39 | item: [Tbjson.TYPES.INSTANCE, Item], 40 | itemsArray: [Tbjson.TYPES.ARRAY, [Tbjson.TYPES.INSTANCE, Item]], 41 | itemsObject: [Tbjson.TYPES.OBJECT, [Tbjson.TYPES.INSTANCE, Item]] 42 | } 43 | }; 44 | 45 | let x = new X(); 46 | 47 | let typedX = Tbjson.cast(JSON.parse(stringify(x)), X); 48 | 49 | let invalid = validateTypes(x, typedX); 50 | if (invalid) { return invalid; } 51 | 52 | return [ 53 | stringify(x), 54 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 55 | ]; 56 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typed-binary-json", 3 | "version": "1.21.2", 4 | "description": "A TBJSON parser and serializer.", 5 | "homepage": "https://github.com/obsius/typed-binary-json", 6 | "author": "Jeff Seaman (obsius.net)", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "files": [ 10 | "/lib" 11 | ], 12 | "scripts": { 13 | "prepare": "rollup --bundleConfigAsCjs -c", 14 | "start": "nodemon --inspect dev.js --exec babel-node", 15 | "benchmarks": "babel-node benchmarks.js", 16 | "test": "babel-node test" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.26.0", 20 | "@babel/node": "^7.26.0", 21 | "@babel/plugin-proposal-class-properties": "^7.18.6", 22 | "@babel/preset-env": "^7.26.0", 23 | "@rollup/plugin-babel": "^6.0.4", 24 | "nodemon": "^3.1.7", 25 | "rollup": "^4.28.0" 26 | }, 27 | "babel": { 28 | "plugins": [ 29 | "@babel/plugin-proposal-class-properties" 30 | ], 31 | "presets": [ 32 | [ 33 | "@babel/preset-env", 34 | { 35 | "targets": { 36 | "node": "current" 37 | } 38 | } 39 | ] 40 | ] 41 | }, 42 | "keywords": [ 43 | "json", 44 | "binary", 45 | "typed", 46 | "parser", 47 | "serializer" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /test/types.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | tbjson.registerTypes([ 6 | new Tbjson.Types.BigIntType(), 7 | new Tbjson.Types.DateType(), 8 | new Tbjson.Types.RegexType() 9 | ]); 10 | 11 | class A { 12 | bigInt = 999_999_999_999_999n; 13 | date = new Date(); 14 | regex = new RegExp('^[0-9]+$', 'i'); 15 | nullableBigInt = -9n; 16 | nullableDate = new Date(0); 17 | nullableRegex = /[0-9]/; 18 | nullBigInt = null; 19 | nullDate = null; 20 | nullRegex = null; 21 | }; 22 | 23 | A.tbjson = { 24 | definition: { 25 | bigInt: Tbjson.Types.BigIntType.ref, 26 | date: Tbjson.Types.DateType.ref, 27 | regex: Tbjson.Types.RegexType.ref, 28 | nullableBigInt: [Tbjson.TYPES.NULLABLE, Tbjson.Types.BigIntType.ref], 29 | nullableDate: [Tbjson.TYPES.NULLABLE, Tbjson.Types.DateType.ref], 30 | nullableRegex: [Tbjson.TYPES.NULLABLE, Tbjson.Types.RegexType.ref], 31 | nullBigInt: [Tbjson.TYPES.NULLABLE, Tbjson.Types.BigIntType.ref], 32 | nullDate: [Tbjson.TYPES.NULLABLE, Tbjson.Types.DateType.ref], 33 | nullRegex: [Tbjson.TYPES.NULLABLE, Tbjson.Types.RegexType.ref] 34 | } 35 | }; 36 | 37 | let x = new A(); 38 | 39 | return [ 40 | stringify(x, true), 41 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x)), true) 42 | ]; 43 | } -------------------------------------------------------------------------------- /test/typed.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class A { 6 | bool = true; 7 | uint8 = 128; 8 | int8 = -127; 9 | uint16 = 2900; 10 | int16 = 3400; 11 | uint32 = 9999999; 12 | int32 = -9999999; 13 | float32 = 1000.123; 14 | float64 = 10000.1234567; 15 | string = '1234567890_+!@#$%^&*()'; 16 | unknownBool = false; 17 | unknownNumber = 10.123; 18 | unknownString = 'unknown'; 19 | unknownNull = null; 20 | array = [false, 100, 999.999]; 21 | } 22 | 23 | tbjson.registerPrototype({ 24 | reference: 'A', 25 | definition: { 26 | bool: Tbjson.TYPES.BOOL, 27 | uint8: Tbjson.TYPES.UINT8, 28 | int8: Tbjson.TYPES.INT8, 29 | uint16: Tbjson.TYPES.UINT16, 30 | int16: Tbjson.TYPES.INT16, 31 | uint32: Tbjson.TYPES.UINT32, 32 | int32: Tbjson.TYPES.INT32, 33 | float32: Tbjson.TYPES.FLOAT32, 34 | float64: Tbjson.TYPES.FLOAT64, 35 | string: Tbjson.TYPES.STRING, 36 | unknownBool: Tbjson.TYPES.UNKNOWN, 37 | unknownNumber: Tbjson.TYPES.UNKNOWN, 38 | unknownString: Tbjson.TYPES.UNKNOWN, 39 | unknownNull: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.UNKNOWN], 40 | array: [Tbjson.TYPES.BOOL, Tbjson.TYPES.INT32, Tbjson.TYPES.FLOAT64] 41 | } 42 | }); 43 | 44 | let x = new A(); 45 | 46 | return [ 47 | stringify(x), 48 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 49 | ]; 50 | } -------------------------------------------------------------------------------- /test/variableDefs.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class A { 6 | int = 999; 7 | string = 'string'; 8 | nullString = null; 9 | array = [10, 10.0001, true, 'apple']; 10 | object = { 11 | float: 10.994, 12 | bool: false 13 | } 14 | } 15 | A.tbjson = { 16 | definition: [Tbjson.TYPES.VARIABLE_DEF, 'var1'] 17 | }; 18 | 19 | class B { 20 | var1 = null; 21 | } 22 | B.tbjson = { 23 | definition: { 24 | var1: [Tbjson.TYPES.NULLABLE, 'var1'] 25 | } 26 | }; 27 | 28 | class C { 29 | a = new A(); 30 | b = new B(); 31 | } 32 | 33 | C.tbjson = { 34 | definition: { 35 | a: A, 36 | b: B 37 | } 38 | }; 39 | 40 | let x = new C(); 41 | 42 | tbjson.registerPrototype(C); 43 | 44 | tbjson.registerPseudoPrototype('var1', { 45 | int: Tbjson.TYPES.INT32, 46 | string: Tbjson.TYPES.STRING, 47 | nullString: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.STRING], 48 | array: Tbjson.TYPES.ARRAY, 49 | object: Tbjson.TYPES.OBJECT 50 | }); 51 | 52 | tbjson.registerVariableDef('var1', { 53 | int: Tbjson.TYPES.INT32, 54 | string: Tbjson.TYPES.STRING, 55 | nullString: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.STRING], 56 | array: Tbjson.TYPES.ARRAY, 57 | object: Tbjson.TYPES.OBJECT 58 | }); 59 | 60 | return [ 61 | stringify(x), 62 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 63 | ]; 64 | } -------------------------------------------------------------------------------- /test/nested.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class A { 6 | uint8 = 255; 7 | float64 = 100000.1234567; 8 | string = 'string'; 9 | array = ['apple', 'bacon', 'grape']; 10 | array2 = [['a1', 'a2', 'a3'], ['b1', 'b2'], ['c1']]; 11 | } 12 | A.tbjson = { 13 | definition: { 14 | uint8: Tbjson.TYPES.UINT8, 15 | float64: Tbjson.TYPES.FLOAT64, 16 | string: Tbjson.TYPES.STRING, 17 | array: [Tbjson.TYPES.ARRAY, Tbjson.TYPES.STRING], 18 | array2: [Tbjson.TYPES.ARRAY, [Tbjson.TYPES.ARRAY, Tbjson.TYPES.STRING]] 19 | } 20 | }; 21 | 22 | class B { 23 | float32 = 100.12345; 24 | a = new A(); 25 | array = [new A(), new A()]; 26 | array2 = [[new A(), new A()], [new A()]]; 27 | object = { 28 | '1': new A(), 29 | '2': new A() 30 | }; 31 | object2 = { 32 | '1': { 33 | '1': new A(), 34 | '2': new A() 35 | }, 36 | '2': { 37 | '1': new A() 38 | } 39 | }; 40 | 41 | as = []; 42 | 43 | constructor() { 44 | for (let i = 0; i < 10; ++i) { 45 | this.as.push(new A()); 46 | } 47 | } 48 | } 49 | B.tbjson = { 50 | definition: { 51 | float32: Tbjson.TYPES.FLOAT32, 52 | a: A, 53 | array: [Tbjson.TYPES.ARRAY, A], 54 | array2: [Tbjson.TYPES.ARRAY, [Tbjson.TYPES.ARRAY, A]], 55 | object: [Tbjson.TYPES.OBJECT, A], 56 | object2: [Tbjson.TYPES.OBJECT, [Tbjson.TYPES.OBJECT, A]], 57 | as: [Tbjson.TYPES.ARRAY, A] 58 | } 59 | }; 60 | 61 | let x = { 62 | b: new B() 63 | }; 64 | 65 | return [ 66 | stringify(x), 67 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 68 | ]; 69 | } -------------------------------------------------------------------------------- /test/typedArray.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | let tbjson = new Tbjson(); 4 | 5 | class A { 6 | uint8 = new Uint8Array(2); 7 | int8 = new Int8Array(4); 8 | uint16 = new Uint16Array(6); 9 | int16 = new Int16Array(9); 10 | uint32 = new Uint32Array(9); 11 | int32 = new Int32Array(8); 12 | float32 = new Float32Array(4); 13 | float64 = new Float64Array(1); 14 | 15 | constructor() { 16 | fillArray(this.uint8); 17 | fillArray(this.int8); 18 | fillArray(this.uint16); 19 | fillArray(this.int16); 20 | fillArray(this.uint32); 21 | fillArray(this.int32); 22 | fillArray(this.float32); 23 | fillArray(this.float64); 24 | } 25 | } 26 | 27 | tbjson.registerPrototype({ 28 | reference: 'A', 29 | definition: { 30 | uint8: [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.UINT8], 31 | int8: [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.INT8], 32 | uint16: [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.UINT16], 33 | int16: [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.INT16], 34 | uint32: [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.UINT32], 35 | int32: [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.INT32], 36 | float32: [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.FLOAT32], 37 | float64: [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.FLOAT64] 38 | } 39 | }); 40 | 41 | let x = { 42 | a: new A(), 43 | uint8: new Uint8Array(2), 44 | int8: new Int8Array(4), 45 | uint16: new Uint16Array(6), 46 | int16: new Int16Array(9), 47 | uint32: new Uint32Array(9), 48 | int32: new Int32Array(8), 49 | float32: new Float32Array(4), 50 | float64: new Float64Array(1) 51 | }; 52 | 53 | return [ 54 | stringify(x), 55 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 56 | ]; 57 | } 58 | 59 | function fillArray(arr) { 60 | for (let i = 0; i < arr.length; ++i) { 61 | arr[i] = i; 62 | } 63 | } -------------------------------------------------------------------------------- /test/nullable.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | class A { 4 | str = 'hello'; 5 | number = 100; 6 | } 7 | 8 | A.tbjson = { 9 | definition: { 10 | str: Tbjson.TYPES.STRING, 11 | number: Tbjson.TYPES.FLOAT32 12 | } 13 | }; 14 | 15 | let tbjson = new Tbjson([], [{ 16 | reference: 'B', 17 | definition: { 18 | bool: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.BOOL], 19 | uint8: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.UINT8], 20 | int8: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.INT8], 21 | uint16: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.UINT16], 22 | int16: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.INT16], 23 | uint32: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.UINT32], 24 | int32: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.INT32], 25 | float32: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.FLOAT32], 26 | float64: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.FLOAT64], 27 | string: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.STRING], 28 | array: [ 29 | [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.BOOL], 30 | [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.INT32], 31 | [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.FLOAT64] 32 | ], 33 | arrayNull: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.ARRAY], 34 | obj: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.OBJECT], 35 | objNull: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.OBJECT], 36 | a: [Tbjson.TYPES.NULLABLE, A], 37 | aNull: [Tbjson.TYPES.NULLABLE, A], 38 | typedArray: [Tbjson.TYPES.NULLABLE, [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.FLOAT32]], 39 | nullTypedArray: [Tbjson.TYPES.NULLABLE, [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.FLOAT32]] 40 | } 41 | }]); 42 | 43 | class B { 44 | bool = null; 45 | uint8 = null; 46 | int8 = null; 47 | uint16 = null; 48 | int16 = null; 49 | uint32 = null; 50 | int32 = null; 51 | float32 = null; 52 | float64 = null; 53 | string = null; 54 | array = [true, 3200000, 9999.123456789]; 55 | arrayNull = null; 56 | obj = { a: 'a' }; 57 | objNull = null; 58 | a = new A(); 59 | aNull = null; 60 | typedArray = new Float32Array([1, 2, 3, 4]); 61 | nullTypedArray = null; 62 | } 63 | 64 | let x = new B(); 65 | 66 | return [ 67 | stringify(x), 68 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 69 | ]; 70 | } -------------------------------------------------------------------------------- /src/functions/serialize.js: -------------------------------------------------------------------------------- 1 | import getParent from '../utility/getParent'; 2 | 3 | /** 4 | * Serialize the typed object into a plain object ignoring typing rules, but obeying which properties should be ignored. 5 | * 6 | * @param { string } obj - object to serialize 7 | */ 8 | export default function serialize(obj, definitions = {}) { 9 | 10 | // object or array 11 | if (obj && typeof obj == 'object') { 12 | 13 | // array 14 | if (Array.isArray(obj)) { 15 | 16 | let retObj = new Array(obj.length); 17 | 18 | for (let i = 0; i < obj.length; ++i) { 19 | retObj[i] = serialize(obj[i], definitions); 20 | } 21 | 22 | return retObj; 23 | 24 | // typed array (no need to check elements as they must all be primitives) 25 | } else if (ArrayBuffer.isView(obj)) { 26 | 27 | let retObj = new Array(obj.length); 28 | 29 | for (let i = 0; i < obj.length; ++i) { 30 | retObj[i] = obj[i]; 31 | } 32 | 33 | return retObj; 34 | 35 | // object 36 | } else { 37 | 38 | let retObj = {}; 39 | 40 | // typed 41 | if (typeof obj.constructor == 'function' && obj.constructor.tbjson && obj.constructor.tbjson.definition) { 42 | 43 | let definition = definitions[obj.constructor.name]; 44 | 45 | // do a lookup for the parent definitions and flatten into one 46 | if (!definition) { 47 | 48 | definition = obj.constructor.tbjson.definition; 49 | 50 | for (let parent = obj.constructor; parent = getParent(parent);) { 51 | if (!parent.tbjson || !parent.tbjson.definition) { break; } 52 | definition = Object.assign({}, parent.tbjson.definition, definition); 53 | } 54 | 55 | definitions[obj.constructor.name] = definition; 56 | } 57 | 58 | let constructor = obj.constructor; 59 | 60 | // unbuild 61 | if (constructor.tbjson.unbuild) { 62 | obj = constructor.tbjson.unbuild(obj); 63 | } 64 | 65 | for (let key in definition) { 66 | retObj[key] = serialize(obj[key], definitions); 67 | } 68 | 69 | // plain 70 | } else { 71 | 72 | for (let key in obj) { 73 | retObj[key] = serialize(obj[key], definitions); 74 | } 75 | } 76 | 77 | return retObj; 78 | } 79 | } 80 | 81 | // primitive 82 | return obj; 83 | } -------------------------------------------------------------------------------- /src/functions/clone.js: -------------------------------------------------------------------------------- 1 | import getParent from '../utility/getParent'; 2 | import cast from './cast'; 3 | 4 | /** 5 | * Clone the typed object into a prototyped object ignoring typing rules, but obeying which properties should be ignored. 6 | * 7 | * @param { string } obj - object to serialize 8 | */ 9 | export default function clone(obj, definitions = {}) { 10 | 11 | // object or array 12 | if (obj && typeof obj == 'object') { 13 | 14 | // array 15 | if (Array.isArray(obj)) { 16 | 17 | let retObj = new Array(obj.length); 18 | 19 | for (let i = 0; i < obj.length; ++i) { 20 | retObj[i] = clone(obj[i], definitions); 21 | } 22 | 23 | return retObj; 24 | 25 | // typed array 26 | } else if (ArrayBuffer.isView(obj)) { 27 | 28 | return obj.slice(); 29 | 30 | // object 31 | } else { 32 | 33 | let retObj = {}; 34 | 35 | // typed 36 | if (typeof obj.constructor == 'function' && obj.constructor.tbjson && obj.constructor.tbjson.definition) { 37 | 38 | let definition = definitions[obj.constructor.name]; 39 | 40 | // do a lookup for the parent definitions and flatten into one 41 | if (!definition) { 42 | 43 | definition = obj.constructor.tbjson.definition; 44 | 45 | for (let parent = obj.constructor; parent = getParent(parent);) { 46 | if (!parent.tbjson || !parent.tbjson.definition) { break; } 47 | definition = Object.assign({}, parent.tbjson.definition, definition); 48 | } 49 | 50 | definitions[obj.constructor.name] = definition; 51 | } 52 | 53 | let constructor = obj.constructor; 54 | 55 | // unbuild 56 | if (constructor.tbjson.unbuild) { 57 | obj = constructor.tbjson.unbuild(obj); 58 | } 59 | 60 | // custom clone function 61 | if (constructor.tbjson.clone) { 62 | retObj = constructor.tbjson.clone(obj); 63 | 64 | // generic clone function 65 | } else { 66 | 67 | for (let key in definition) { 68 | retObj[key] = clone(obj[key], definitions); 69 | } 70 | 71 | // cast 72 | retObj = cast(retObj, constructor); 73 | } 74 | 75 | // date object 76 | } else if (obj instanceof Date) { 77 | retObj = new Date(obj.getTime()); 78 | 79 | // plain 80 | } else { 81 | 82 | for (let key in obj) { 83 | retObj[key] = clone(obj[key], definitions); 84 | } 85 | } 86 | 87 | return retObj; 88 | } 89 | } 90 | 91 | // primitive 92 | return obj; 93 | } -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | // magic number for file type 2 | export const MAGIC_NUMBER = '.tbj'; 3 | export const SIZE_MAGIC_NUMBER = 4; 4 | 5 | export const VERSION = 1; 6 | 7 | // error 8 | export const ERROR = -1; 9 | 10 | // primitive types 11 | export const NULL = 0; 12 | export const BOOL = 1; 13 | export const UINT8 = 2; 14 | export const INT8 = 3; 15 | export const UINT16 = 4; 16 | export const INT16 = 5; 17 | export const UINT32 = 6; 18 | export const INT32 = 7; 19 | export const FLOAT32 = 8; 20 | export const FLOAT64 = 9; 21 | 22 | // higher-order types 23 | export const STRING = 10; 24 | export const ARRAY = 11; 25 | export const OBJECT = 12; 26 | export const NULLABLE = 13; 27 | export const TYPED_ARRAY = 14; 28 | export const UNKNOWN = 15; 29 | 30 | // extras 31 | export const VARIABLE_DEF = 16; 32 | export const INSTANCE = 17; 33 | 34 | // primitive sizes 35 | export const SIZE_NULL = 1; 36 | export const SIZE_BOOL = 1; 37 | export const SIZE_INT8 = 1; 38 | export const SIZE_UINT8 = 1; 39 | export const SIZE_INT16 = 2; 40 | export const SIZE_UINT16 = 2; 41 | export const SIZE_INT32 = 4; 42 | export const SIZE_UINT32 = 4; 43 | export const SIZE_FLOAT32 = 4; 44 | export const SIZE_FLOAT64 = 8; 45 | 46 | // offsets 47 | export const NULLABLE_OFFSET = 16; // support 15 primitives (NULL is reserved and INSTANCE is only for casting) 48 | export const TYPED_ARRAY_OFFSET = 32; 49 | export const TYPE_OFFSET = 48; 50 | export const PROTOTYPE_OFFSET = 64; // support up to 16 types 51 | export const NULLABLE_PROTOTYPE_OFFSET = 256; // support up to 192 prototypes 52 | export const ARRAY_OFFSET = 512; 53 | export const OBJECT_OFFSET = 4096; // support 4x nested array 54 | 55 | // legacy offsets 56 | export const L_NULLABLE_PROTOTYPE_OFFSET = 160; 57 | export const L_ARRAY_OFFSET = 256; 58 | export const L_OBJECT_OFFSET = 1024; 59 | 60 | // defaults 61 | export const DEFAULT_BUFFER_SIZE = 1048576; 62 | export const DEFAULT_NUM_ENCODING = FLOAT64; 63 | export const DEFAULT_STR_ENCODING = 'utf8'; 64 | export const DEFAULT_X_FACTOR = 2; -------------------------------------------------------------------------------- /src/utility/StreamBufferReader.js: -------------------------------------------------------------------------------- 1 | import { 2 | UINT32, 3 | FLOAT32, 4 | STRING, 5 | 6 | SIZE_UINT32, 7 | SIZE_FLOAT32, 8 | 9 | DEFAULT_BUFFER_SIZE, 10 | DEFAULT_STR_ENCODING 11 | } from '../constants'; 12 | 13 | export default class StreamBufferReader { 14 | 15 | constructor(stream, size = DEFAULT_BUFFER_SIZE, strEncoding = DEFAULT_STR_ENCODING) { 16 | 17 | this.stream = stream; 18 | this.size = size; 19 | this.strEncoding = strEncoding; 20 | 21 | this.tempSize = size; 22 | this.buffer = Buffer.allocUnsafe(size); 23 | 24 | this.writeOffset = 0; 25 | this.readOffset = 0; 26 | 27 | this.stream.on('data', (chunk) => { 28 | 29 | if (this.writeOffset + chunk.length > this.tempSize) { 30 | this.stream.pause(); 31 | } 32 | 33 | this.buffer.fill(chunk, this.writeOffset, this.writeOffset + chunk.length); 34 | this.writeOffset += chunk.length; 35 | 36 | if (this.waitingRead) { 37 | this.waitingRead(); 38 | } 39 | }); 40 | } 41 | 42 | readUntilNull(fn) { 43 | for (let i = this.readOffset; i < this.buffer.length; ++i) { 44 | if (this.buffer[i] == null) { 45 | fn(this.buffer.slice(this.offset, i)); 46 | this.incReadOffset(i - this.readOffset); 47 | } 48 | } 49 | } 50 | 51 | read(type, length = 0) { 52 | switch (type) { 53 | case UINT32: 54 | this.readBytes(SIZE_UINT32, (readOffset) => fn(this.buffer.readUInt32(readOffset))); 55 | break; 56 | 57 | case FLOAT32: 58 | this.readBytes(SIZE_FLOAT32, (readOffset) => fn(this.buffer.readFloat32(readOffset))); 59 | break; 60 | 61 | case STRING: 62 | if (length) { 63 | this.readBytes(length, (readOffset) => fn(this.buffer.toString(this.strEncoding, readOffset, length))); 64 | } else { 65 | this.readUntilNull(); 66 | } 67 | } 68 | } 69 | 70 | /* internal */ 71 | 72 | incReadOffset(length) { 73 | this.readOffset += length; 74 | 75 | if (this.readOffset > this.size) { 76 | 77 | this.writeOffset = this.buffer.length - this.writeOffset; 78 | this.readOffset = 0; 79 | 80 | this.newBuffer = Buffer.allocUnsafe(this.size); 81 | this.newBuffer.fill(this.offset, this.buffer.length); 82 | this.buffer = this.newBuffer; 83 | 84 | if (this.stream.isPaused()) { this.stream.resume(); } 85 | } 86 | } 87 | 88 | readBytes(length) { 89 | if (this.readOffset + length > this.writeOffset) { 90 | return new Promise((res, rej) => { 91 | 92 | if (this.size < this.readOffset + length) { 93 | this.tmpSize = this.readOffset + length; 94 | } 95 | 96 | this.waitingRead = () => { 97 | this.tempSize = this.size; 98 | this.readBytes(length, fn); 99 | }; 100 | }); 101 | } else { 102 | let readOffset = this.readOffset; 103 | this.incReadOffset(length); 104 | return readOffset; 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /test/cast.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify, validateTypes) { 2 | 3 | class A { 4 | bool = true; 5 | uint8 = 128; 6 | int8 = -127; 7 | uint16 = 2900; 8 | int16 = 3400; 9 | uint32 = 9999999; 10 | int32 = -9999999; 11 | float32 = 1000.123; 12 | float64 = 10000.1234567; 13 | string = '1234567890_+!@#$%^&*()'; 14 | arrayTyped = [false, 100, 999.999]; 15 | arrayUntyped = [false]; 16 | object = { key: 'value' }; 17 | unknownBool = false; 18 | unknownNumber = 10.123; 19 | unknownString = 'unknown'; 20 | unknownNull = null; 21 | } 22 | 23 | A.tbjson = { 24 | definition: { 25 | bool: Tbjson.TYPES.BOOL, 26 | uint8: Tbjson.TYPES.UINT8, 27 | int8: Tbjson.TYPES.INT8, 28 | uint16: Tbjson.TYPES.UINT16, 29 | int16: Tbjson.TYPES.INT16, 30 | uint32: Tbjson.TYPES.UINT32, 31 | int32: Tbjson.TYPES.INT32, 32 | float32: Tbjson.TYPES.FLOAT32, 33 | float64: Tbjson.TYPES.FLOAT64, 34 | string: Tbjson.TYPES.STRING, 35 | arrayTyped: [Tbjson.TYPES.BOOL, Tbjson.TYPES.INT32, Tbjson.TYPES.FLOAT64], 36 | arrayUntyped: Tbjson.TYPES.ARRAY, 37 | object: Tbjson.TYPES.OBJECT, 38 | unknownBool: Tbjson.TYPES.UNKNOWN, 39 | unknownNumber: Tbjson.TYPES.UNKNOWN, 40 | unknownString: Tbjson.TYPES.UNKNOWN, 41 | unknownNull: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.UNKNOWN] 42 | } 43 | }; 44 | 45 | class B { 46 | array = [new A(), new A(), new A()]; 47 | array2 = [[new A(), new A()], [new A()]]; 48 | object = { 49 | '1': new A(), 50 | '2': new A(), 51 | '3': new A() 52 | }; 53 | object2 = { 54 | '1': { 55 | '1': new A(), 56 | '2': new A() 57 | }, 58 | '2': { 59 | '1': new A() 60 | } 61 | }; 62 | nullableAndNullA = null; 63 | nullableAndNotA = new A(); 64 | } 65 | B.tbjson = { 66 | definition: { 67 | array: [Tbjson.TYPES.ARRAY, A], 68 | array2: [Tbjson.TYPES.ARRAY, [Tbjson.TYPES.ARRAY, A]], 69 | object: [Tbjson.TYPES.OBJECT, A], 70 | object2: [Tbjson.TYPES.OBJECT, [Tbjson.TYPES.OBJECT, A]], 71 | nullableAndNullA: [Tbjson.TYPES.NULLABLE, A], 72 | nullableAndNotA: [Tbjson.TYPES.NULLABLE, A] 73 | } 74 | }; 75 | 76 | class C { 77 | a = new A(); 78 | b = new B(); 79 | } 80 | C.tbjson = { 81 | definition: { 82 | a: A, 83 | b: B 84 | } 85 | }; 86 | 87 | class D { 88 | a = new A(); 89 | b = new B(); 90 | c = new C(); 91 | mixed = [new A(), new B(), new C()]; 92 | nested = { 93 | a: new A(), 94 | b: new B(), 95 | c: new C() 96 | }; 97 | } 98 | D.tbjson = { 99 | definition: { 100 | a: A, 101 | b: B, 102 | c: C, 103 | mixed: [A, B, C], 104 | nested: { 105 | a: A, 106 | b: B, 107 | c: C 108 | } 109 | } 110 | }; 111 | 112 | class E extends D { 113 | string = 'string'; 114 | } 115 | E.tbjson = { 116 | definition: { 117 | string: Tbjson.TYPES.STRING 118 | } 119 | }; 120 | 121 | let x = new E(); 122 | 123 | let typedX = Tbjson.cast(JSON.parse(stringify(x)), E, undefined, true); 124 | 125 | let invalid = validateTypes(x, typedX); 126 | if (invalid) { return invalid; } 127 | 128 | return [ 129 | stringify(x), 130 | stringify(typedX) 131 | ]; 132 | } -------------------------------------------------------------------------------- /src/utility/BufferReader.js: -------------------------------------------------------------------------------- 1 | import { 2 | BOOL, 3 | UINT8, 4 | INT8, 5 | UINT16, 6 | INT16, 7 | UINT32, 8 | INT32, 9 | FLOAT32, 10 | FLOAT64, 11 | STRING, 12 | UNKNOWN, 13 | 14 | SIZE_UINT8, 15 | SIZE_INT8, 16 | SIZE_UINT16, 17 | SIZE_INT16, 18 | SIZE_UINT32, 19 | SIZE_INT32, 20 | SIZE_FLOAT32, 21 | SIZE_FLOAT64, 22 | 23 | DEFAULT_STR_ENCODING 24 | } from '../constants'; 25 | 26 | export default class BufferReader { 27 | 28 | offset = 0; 29 | 30 | constructor(buffer, strEncoding = DEFAULT_STR_ENCODING) { 31 | this.buffer = buffer; 32 | this.strEncoding = strEncoding; 33 | } 34 | 35 | read(type) { 36 | let data; 37 | 38 | switch (type) { 39 | 40 | case BOOL: 41 | data = !!this.buffer.readUInt8(this.offset); 42 | this.offset += SIZE_UINT8; 43 | break; 44 | 45 | case UINT8: 46 | data = this.buffer.readUInt8(this.offset); 47 | this.offset += SIZE_UINT8; 48 | break; 49 | 50 | case INT8: 51 | data = this.buffer.readInt8(this.offset); 52 | this.offset += SIZE_INT8; 53 | break; 54 | 55 | case UINT16: 56 | data = this.buffer.readUInt16BE(this.offset); 57 | this.offset += SIZE_UINT16; 58 | break; 59 | 60 | case INT16: 61 | data = this.buffer.readInt16BE(this.offset); 62 | this.offset += SIZE_INT16; 63 | break; 64 | 65 | case UINT32: 66 | data = this.buffer.readUInt32BE(this.offset); 67 | this.offset += SIZE_UINT32; 68 | break; 69 | 70 | case INT32: 71 | data = this.buffer.readInt32BE(this.offset); 72 | this.offset += SIZE_INT32; 73 | break; 74 | 75 | case FLOAT32: 76 | data = this.buffer.readFloatBE(this.offset); 77 | this.offset += SIZE_FLOAT32; 78 | break; 79 | 80 | case FLOAT64: 81 | data = this.buffer.readDoubleBE(this.offset); 82 | this.offset += SIZE_FLOAT64; 83 | break; 84 | 85 | case STRING: 86 | 87 | let length; 88 | 89 | if (this.buffer[this.offset] < 128) { 90 | length = this.read(UINT8); 91 | } else if (this.buffer[this.offset] < 192) { 92 | length = this.read(UINT16) - 32768; 93 | } else { 94 | length = this.read(UINT32) - 3221225472; 95 | } 96 | 97 | data = this.buffer.toString(this.strEncoding, this.offset, this.offset + length); 98 | 99 | this.offset += length; 100 | 101 | break; 102 | 103 | case UNKNOWN: 104 | data = this.read(this.read(UINT8)); 105 | } 106 | 107 | return data; 108 | } 109 | 110 | readBuffer(length) { 111 | 112 | let buffer = this.buffer.slice(this.offset, this.offset + length); 113 | this.offset += length; 114 | 115 | return buffer; 116 | } 117 | 118 | readFixedLengthString(length) { 119 | 120 | let data = this.buffer.toString(this.strEncoding, this.offset, this.offset + length); 121 | this.offset += length; 122 | 123 | return data; 124 | } 125 | 126 | readTypedArray(type, length) { 127 | 128 | let byteOffset = this.buffer.byteOffset + this.offset; 129 | let buffer = this.buffer.buffer.slice(byteOffset, byteOffset + length); 130 | this.offset += length; 131 | 132 | switch (type) { 133 | case UINT8: 134 | return new Uint8Array(buffer); 135 | case INT8: 136 | return new Int8Array(buffer); 137 | case UINT16: 138 | return new Uint16Array(buffer); 139 | case INT16: 140 | return new Int16Array(buffer); 141 | case UINT32: 142 | return new Uint32Array(buffer); 143 | case INT32: 144 | return new Int32Array(buffer); 145 | case FLOAT32: 146 | return new Float32Array(buffer); 147 | case FLOAT64: 148 | return new Float64Array(buffer); 149 | } 150 | } 151 | 152 | /* internal */ 153 | 154 | nextNullAt() { 155 | for (let i = this.offset; i < this.buffer.length; ++i) { 156 | if (!this.buffer[i]) { return i; } 157 | } 158 | 159 | throw new Error('BufferReader could not find a null value'); 160 | } 161 | } -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import Tbjson from '../src/Tbjson'; 2 | 3 | import cast from './cast'; 4 | import clone from './clone'; 5 | import definition from './definition'; 6 | import flattenValidation from './flattenValidation'; 7 | import inheritance from './inheritance'; 8 | import nested from './nested'; 9 | import nestedDefinition from './nestedDefinition'; 10 | import nulls from './nulls'; 11 | import nullable from './nullable'; 12 | import plain from './plain.js'; 13 | import plainInTyped from './plainInTyped'; 14 | import prototype from './prototype'; 15 | import prototypeCast from './prototypeCast'; 16 | import selection from './selection'; 17 | import serialize from './serialize'; 18 | import strings from './strings'; 19 | import typed from './typed.js'; 20 | import typedArray from './typedArray'; 21 | import typedSelection from './typedSelection'; 22 | import types from './types.js'; 23 | import typesCast from './typesCast.js'; 24 | import unbuild from './unbuild'; 25 | import validate from './validate'; 26 | import variableDefs from './variableDefs'; 27 | 28 | function stringify(val, cast = false) { 29 | return JSON.stringify(val, (name, val) => { 30 | 31 | if (cast) { 32 | if (typeof val == 'bigint') { 33 | val = Number(val); 34 | } else if (val instanceof Date || val instanceof RegExp) { 35 | val = val.toString(); 36 | } 37 | } 38 | 39 | if (typeof val == 'number') { 40 | return +val.toFixed(3); 41 | } 42 | 43 | return val; 44 | }); 45 | } 46 | 47 | function validateTypes(obj1, obj2, path = '$') { 48 | if (obj1 && typeof obj1 == 'object') { 49 | 50 | if (!obj2 || typeof obj2 != 'object' || obj1.constructor != obj2.constructor) { 51 | return path; 52 | } 53 | 54 | if (Array.isArray(obj1)) { 55 | 56 | if (!Array.isArray(obj2)) { 57 | return path; 58 | } 59 | 60 | for (let i = 0; i < obj1.length; ++i) { 61 | let invalid = validateTypes(obj1[i], obj2[i], `${path}[${i}]`); 62 | if (invalid) { return invalid; } 63 | } 64 | } else { 65 | for (let key in obj1) { 66 | let invalid = validateTypes(obj1[key], obj2[key], `${path}.${key}`); 67 | if (invalid) { return invalid; } 68 | } 69 | } 70 | } 71 | } 72 | 73 | function run() { 74 | runTest('Cast', cast); 75 | runTest('Clone', clone); 76 | runTest('Definition', definition); 77 | runTest('Inheritance', inheritance); 78 | runTest('Nested', nested); 79 | runTest('Nested Definition', nestedDefinition); 80 | runTest('Nulls', nulls); 81 | runTest('Nullable', nullable); 82 | runTest('Plain', plain); 83 | runTest('Plain In Typed', plainInTyped); 84 | runTest('Prototype', prototype); 85 | runTest('prototypeCast', prototypeCast); 86 | runTest('Selection', selection); 87 | runTest('Serialize', serialize); 88 | runTest('Strings', strings); 89 | runTest('Typed', typed); 90 | runTest('Typed Array', typedArray); 91 | runTest('Typed Selection', typedSelection); 92 | runTest('Types', types); 93 | runTest('Types Cast', typesCast); 94 | runTest('Unbuild', unbuild); 95 | runTest('Validate', validate); 96 | runTest('Validation Flattened', flattenValidation); 97 | runTest('Variable Definitions', variableDefs); 98 | } 99 | 100 | function runTest(name, fn) { 101 | try { 102 | 103 | console.log(`Running ${name}...`); 104 | 105 | let startTime = (new Date()).getTime(); 106 | 107 | let results = fn(Tbjson, stringify, validateTypes); 108 | 109 | let time = (new Date()).getTime() - startTime; 110 | 111 | let success = false; 112 | let msg; 113 | 114 | if (Array.isArray(results)) { 115 | success = results[0] == results[1]; 116 | } else if (typeof results == 'string') { 117 | success = false; 118 | msg = results; 119 | } else { 120 | success = results; 121 | } 122 | 123 | if (success) { 124 | console.log(`Passed [${time} ms]`); 125 | } else { 126 | console.log(`Failed [${time} ms]`); 127 | console.log(msg ? msg : results); 128 | } 129 | } catch (e) { 130 | console.log('Error running test: ', e); 131 | } 132 | } 133 | 134 | run(); -------------------------------------------------------------------------------- /src/utility/BufferWriter.js: -------------------------------------------------------------------------------- 1 | import { 2 | NULL, 3 | BOOL, 4 | UINT8, 5 | INT8, 6 | UINT16, 7 | INT16, 8 | UINT32, 9 | INT32, 10 | FLOAT32, 11 | FLOAT64, 12 | STRING, 13 | UNKNOWN, 14 | 15 | SIZE_UINT8, 16 | SIZE_INT8, 17 | SIZE_UINT16, 18 | SIZE_INT16, 19 | SIZE_UINT32, 20 | SIZE_INT32, 21 | SIZE_FLOAT32, 22 | SIZE_FLOAT64, 23 | 24 | DEFAULT_BUFFER_SIZE, 25 | DEFAULT_X_FACTOR, 26 | DEFAULT_STR_ENCODING 27 | } from '../constants'; 28 | 29 | const MAX_BYTES_PER_CHAR_UNICODE = 4; 30 | const MAX_BYTES_PER_CHAR_ASCII = 1; 31 | 32 | export default class BufferWriter { 33 | 34 | offset = 0; 35 | 36 | get size() { 37 | return this.buffer.length; 38 | } 39 | 40 | constructor(size = DEFAULT_BUFFER_SIZE, xFactor = DEFAULT_X_FACTOR, strEncoding = DEFAULT_STR_ENCODING) { 41 | 42 | this.buffer = Buffer.allocUnsafe(size); 43 | this.xFactor = xFactor; 44 | this.strEncoding = strEncoding; 45 | 46 | if (strEncoding == 'asci') { 47 | this.maxBytesPerChar = MAX_BYTES_PER_CHAR_ASCII; 48 | } else { 49 | this.maxBytesPerChar = MAX_BYTES_PER_CHAR_UNICODE; 50 | } 51 | } 52 | 53 | getBuffer() { 54 | return this.buffer.slice(0, this.offset); 55 | } 56 | 57 | grow(size) { 58 | do { 59 | this.buffer = Buffer.concat([this.buffer, Buffer.allocUnsafe(this.size * Math.floor(this.xFactor / 2))]); 60 | } while (size && this.offset + size > this.size); 61 | } 62 | 63 | write(type, val) { 64 | switch (type) { 65 | 66 | case NULL: 67 | val = 0; 68 | case BOOL: 69 | case UINT8: 70 | this.checkSize(SIZE_UINT8); 71 | this.buffer.writeUInt8(val, this.offset); 72 | this.offset += SIZE_UINT8; 73 | break; 74 | 75 | case INT8: 76 | this.checkSize(SIZE_INT8); 77 | this.buffer.writeInt8(val, this.offset); 78 | this.offset += SIZE_INT8; 79 | break; 80 | 81 | case UINT16: 82 | this.checkSize(SIZE_UINT16); 83 | this.buffer.writeUInt16BE(val, this.offset); 84 | this.offset += SIZE_UINT16; 85 | break; 86 | 87 | case INT16: 88 | this.checkSize(SIZE_INT16); 89 | this.buffer.writeInt16BE(val, this.offset); 90 | this.offset += SIZE_INT16; 91 | break; 92 | 93 | case UINT32: 94 | this.checkSize(SIZE_UINT32); 95 | this.buffer.writeUInt32BE(val, this.offset); 96 | this.offset += SIZE_UINT32; 97 | break; 98 | 99 | case INT32: 100 | this.checkSize(SIZE_INT32); 101 | this.buffer.writeInt32BE(val, this.offset); 102 | this.offset += SIZE_INT32; 103 | break; 104 | 105 | case FLOAT32: 106 | this.checkSize(SIZE_FLOAT32); 107 | this.buffer.writeFloatBE(val, this.offset); 108 | this.offset += SIZE_FLOAT32; 109 | break; 110 | 111 | case FLOAT64: 112 | this.checkSize(SIZE_FLOAT64); 113 | this.buffer.writeDoubleBE(val, this.offset); 114 | this.offset += SIZE_FLOAT64; 115 | break; 116 | 117 | case STRING: 118 | 119 | // empty string 120 | if (typeof val != 'string' || !val.length) { 121 | this.write(UINT8, 0); 122 | 123 | // variable length / encoded string 124 | } else { 125 | 126 | // max possible size 127 | let size = val.length * this.maxBytesPerChar; 128 | let lengthSize = (size < 128 ? 1 : size < 16384 ? 2 : 4); 129 | 130 | this.checkSize(size + lengthSize); 131 | 132 | // write out string and pre-allocate bytes to hold string length 133 | size = this.buffer.write(val, this.offset + lengthSize, size, this.strEncoding); 134 | 135 | // R00000000 first bit reserved for signaling to use 2 bytes, max string length is: 2^7 = 128 136 | if (lengthSize == 1) { 137 | this.buffer.writeUInt8(size, this.offset); 138 | 139 | // 1R000000 first bit must be 1, second bit reserved for signaling to use 4 bytes, max string length is: 2^16 - (2^15 + 2^14) = 16384 140 | } else if (lengthSize == 2) { 141 | this.buffer.writeUInt16BE(size + 32768, this.offset); 142 | 143 | // 11000000 first two bits must be 1, max string length is: 2^32 - (2^31 + 2^30) = 1073741824 144 | } else { 145 | this.buffer.writeUInt32BE(size + 3221225472, this.offset); 146 | } 147 | 148 | this.offset += size + lengthSize; 149 | } 150 | break; 151 | 152 | case UNKNOWN: 153 | switch (typeof val) { 154 | 155 | case 'boolean': 156 | this.write(UINT8, BOOL); 157 | this.write(BOOL, val); 158 | break; 159 | 160 | case 'number': 161 | this.write(UINT8, FLOAT64); 162 | this.write(FLOAT64, val); 163 | break; 164 | 165 | case 'string': 166 | this.write(UINT8, STRING); 167 | this.write(STRING, val); 168 | break; 169 | 170 | default: 171 | this.write(NULL); 172 | } 173 | } 174 | } 175 | 176 | writeBuffer(buffer) { 177 | this.checkSize(buffer.length); 178 | buffer.copy(this.buffer, this.offset); 179 | this.offset += buffer.length; 180 | } 181 | 182 | writeFixedLengthString(val) { 183 | this.checkSize(val.length * this.maxBytesPerChar); 184 | this.offset += this.buffer.write(val, this.offset, val.length * this.maxBytesPerChar, this.strEncoding); 185 | } 186 | 187 | /* internal */ 188 | 189 | checkSize(size) { 190 | if (this.offset + size > this.buffer.length) { 191 | this.grow(size); 192 | } 193 | } 194 | } -------------------------------------------------------------------------------- /test/validate.js: -------------------------------------------------------------------------------- 1 | export default function(Tbjson, stringify) { 2 | 3 | class A { 4 | validBool = true; 5 | validUint8 = 128; 6 | validInt8 = -127; 7 | validInt16 = 3400; 8 | validUint16 = 10000; 9 | validUint32 = 9999999; 10 | validInt32 = -9999999; 11 | validFloat32 = 1000.123; 12 | validFloat64 = 10000.1234567; 13 | validString = 'string'; 14 | validFloat64Array = [0, 1, 2]; 15 | validFloat64Object = { a: 1, b: 2 }; 16 | validFloat64TypedArray = new Float64Array([0, 1, 2]); 17 | validFloat64ArrayArray = [[0, 1, 2], [0, 1, 2]]; 18 | validFloat64ObjectObject = {a: { a: 1, b: 2 }, b: { a: 1, b: 2 }}; 19 | 20 | invalidBool1 = 12; 21 | invalidBool2 = ''; 22 | invalidUint8 = -1; 23 | invalidInt8 = false; 24 | invalidUint16 = '123'; 25 | invalidInt16 = null; 26 | invalidUint32 = {}; 27 | invalidInt32 = Number.MAX_SAFE_INTEGER; 28 | invalidFloat32 = /regex/; 29 | invalidFloat64 = Number.NaN; 30 | invalidString = 12; 31 | invalidFloat64Array = ['a', 1, 2]; 32 | invalidFloat64Object = { a: 1, b: false }; 33 | invalidFloat64TypedArray = new Float64Array([0, 1, null]); 34 | invalidFloat64ArrayArray = [[0, 1, 2], [1, 2, true]]; 35 | invalidFloat64ObjectObject = {a: { a: 1, b: 2 }, b: { a: 1, b: null }}; 36 | } 37 | 38 | A.tbjson = { 39 | definition: { 40 | validBool: Tbjson.TYPES.BOOL, 41 | validUint8: Tbjson.TYPES.UINT8, 42 | validInt8: Tbjson.TYPES.INT8, 43 | validUint16: Tbjson.TYPES.UINT16, 44 | validInt16: Tbjson.TYPES.INT16, 45 | validUint32: Tbjson.TYPES.UINT32, 46 | validInt32: Tbjson.TYPES.INT32, 47 | validFloat32: Tbjson.TYPES.FLOAT32, 48 | validFloat64: Tbjson.TYPES.FLOAT64, 49 | validString: Tbjson.TYPES.STRING, 50 | validFloat64Array: [Tbjson.TYPES.ARRAY, Tbjson.TYPES.FLOAT64], 51 | validFloat64Object: [Tbjson.TYPES.OBJECT, Tbjson.TYPES.FLOAT64], 52 | validFloat64TypedArray: [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.FLOAT64], 53 | validFloat64ArrayArray: [Tbjson.TYPES.ARRAY, [Tbjson.TYPES.ARRAY, Tbjson.TYPES.FLOAT64]], 54 | validFloat64ObjectObject: [Tbjson.TYPES.OBJECT, [Tbjson.TYPES.OBJECT, Tbjson.TYPES.FLOAT64]], 55 | 56 | invalidBool1: Tbjson.TYPES.BOOL, 57 | invalidBool2: Tbjson.TYPES.BOOL, 58 | invalidUint8: Tbjson.TYPES.UINT8, 59 | invalidInt8: Tbjson.TYPES.INT8, 60 | invalidUint16: Tbjson.TYPES.UINT16, 61 | invalidInt16: Tbjson.TYPES.INT16, 62 | invalidUint32: Tbjson.TYPES.UINT32, 63 | invalidInt32: Tbjson.TYPES.INT32, 64 | invalidFloat32: Tbjson.TYPES.FLOAT32, 65 | invalidFloat64: Tbjson.TYPES.FLOAT64, 66 | invalidString: Tbjson.TYPES.STRING, 67 | invalidFloat64Array: [Tbjson.TYPES.ARRAY, Tbjson.TYPES.FLOAT64], 68 | invalidFloat64Object: [Tbjson.TYPES.OBJECT, Tbjson.TYPES.FLOAT64], 69 | invalidFloat64TypedArray: [Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.FLOAT64], 70 | invalidFloat64ArrayArray: [Tbjson.TYPES.ARRAY, [Tbjson.TYPES.ARRAY, Tbjson.TYPES.FLOAT64]], 71 | invalidFloat64ObjectObject: [Tbjson.TYPES.OBJECT, [Tbjson.TYPES.OBJECT, Tbjson.TYPES.FLOAT64]] 72 | } 73 | }; 74 | 75 | class B extends A { 76 | validNullableString = null; 77 | invalidNonNullableString = null; 78 | } 79 | B.tbjson = { 80 | definition: { 81 | validNullableString: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.STRING], 82 | invalidNonNullableString: Tbjson.TYPES.STRING 83 | } 84 | }; 85 | 86 | class C { 87 | invalidNullaleFloat64 = 'a'; 88 | } 89 | C.tbjson = { 90 | definition: { 91 | invalidNullaleFloat64: [Tbjson.TYPES.NULLABLE, Tbjson.TYPES.FLOAT64] 92 | } 93 | }; 94 | 95 | class D { 96 | b = new B(); 97 | array = [new C(), new C()]; 98 | object = { 99 | [1]: new C(), 100 | [2]: new C() 101 | }; 102 | mixed = [new C(), new C()]; 103 | nested = { 104 | a: new C(), 105 | b: new C() 106 | }; 107 | } 108 | D.tbjson = { 109 | definition: { 110 | b: B, 111 | array: [Tbjson.TYPES.ARRAY, C], 112 | object: [Tbjson.TYPES.OBJECT, C], 113 | mixed: [C, C], 114 | nested: { 115 | a: C, 116 | b: C 117 | } 118 | } 119 | }; 120 | 121 | let x = new D(); 122 | 123 | return [ 124 | stringify(Tbjson.validate(x)), 125 | stringify([ 126 | { 127 | "b": { 128 | "invalidBool1": 1, 129 | "invalidBool2": 1, 130 | "invalidUint8": 2, 131 | "invalidInt8": 3, 132 | "invalidUint16": 4, 133 | "invalidInt16": 5, 134 | "invalidUint32": 6, 135 | "invalidInt32": 7, 136 | "invalidFloat32": 8, 137 | "invalidFloat64": 9, 138 | "invalidString": 10, 139 | "invalidFloat64Array": { 140 | "0": 9 141 | }, 142 | "invalidFloat64Object": { 143 | "b": 9 144 | }, 145 | "invalidFloat64ArrayArray": { 146 | "1": { 147 | "2": 9 148 | } 149 | }, 150 | "invalidFloat64ObjectObject": { 151 | "b": { 152 | "b": 9 153 | } 154 | }, 155 | "invalidNonNullableString": 10 156 | }, 157 | "array": { 158 | "0": { 159 | "invalidNullaleFloat64": 9 160 | }, 161 | "1": { 162 | "invalidNullaleFloat64": 9 163 | } 164 | }, 165 | "object": { 166 | "1": { 167 | "invalidNullaleFloat64": 9 168 | }, 169 | "2": { 170 | "invalidNullaleFloat64": 9 171 | } 172 | }, 173 | "mixed": { 174 | "0": { 175 | "invalidNullaleFloat64": 9 176 | }, 177 | "1": { 178 | "invalidNullaleFloat64": 9 179 | } 180 | }, 181 | "nested": { 182 | "a": { 183 | "invalidNullaleFloat64": 9 184 | }, 185 | "b": { 186 | "invalidNullaleFloat64": 9 187 | } 188 | } 189 | }, 190 | 24 191 | ]) 192 | ]; 193 | } -------------------------------------------------------------------------------- /src/functions/cast.js: -------------------------------------------------------------------------------- 1 | import { 2 | BOOL, 3 | STRING, 4 | ARRAY, 5 | OBJECT, 6 | NULLABLE, 7 | VARIABLE_DEF, 8 | INSTANCE 9 | } from '../constants'; 10 | 11 | import getParent from '../utility/getParent'; 12 | 13 | /** 14 | * Cast a plain object into the typed object it represents. Only supports prototype definitions, not strings. 15 | * 16 | * @param { object } obj - object to parse 17 | * @param { function } prototype - prototype to cast into 18 | * @param { array } types - array of types with deserializers 19 | * @param { bool } castPrimitives - also cast primitives (bool, number, string) 20 | * @param { bool } freeMemory - set obj properties to undefined as the obj is cast (slower, but frees up memory) 21 | */ 22 | export default function cast(obj, prototype, types, castPrimitives = false, freeMemory = false, definitions = {}) { 23 | 24 | // plain object or array with a definition (ignore prototyped) 25 | if (prototype && (typeof prototype == 'function' || typeof prototype == 'object')) { 26 | 27 | let isNonNullObject = typeof obj == 'object' && obj; 28 | 29 | let isArray = Array.isArray(prototype); 30 | let isArrayTypeDef = Array.isArray(prototype) && prototype.length == 2; 31 | 32 | // array 33 | if (Array.isArray(obj) && isArray) { 34 | 35 | let typedObj; 36 | 37 | // typed array 38 | if (isArrayTypeDef && prototype[0] == ARRAY) { 39 | 40 | typedObj = new Array(obj.length); 41 | 42 | for (let i = 0; i < obj.length; ++i) { 43 | typedObj[i] = cast(obj[i], prototype[1], types, castPrimitives, freeMemory, definitions); 44 | if (freeMemory) { obj[i] = undefined; } 45 | } 46 | 47 | // unknown array 48 | } else { 49 | 50 | typedObj = new Array(prototype.length); 51 | 52 | for (let i = 0; i < prototype.length; ++i) { 53 | typedObj[i] = cast(obj[i], prototype[i], types, castPrimitives, freeMemory, definitions); 54 | if (freeMemory) { obj[i] = undefined; } 55 | } 56 | } 57 | 58 | return typedObj; 59 | 60 | // qualified type 61 | } else if (isArrayTypeDef) { 62 | 63 | switch (prototype[0]) { 64 | 65 | // uniform value object 66 | case OBJECT: 67 | 68 | let typedObj = {}; 69 | 70 | if (isNonNullObject) { 71 | for (let key in obj) { 72 | typedObj[key] = cast(obj[key], prototype[1], types, castPrimitives, freeMemory, definitions); 73 | if (freeMemory) { obj[key] = undefined; } 74 | } 75 | } 76 | 77 | return typedObj; 78 | 79 | // nullable object 80 | case NULLABLE: 81 | return obj == null ? null : cast(obj, prototype[1], types, castPrimitives, freeMemory, definitions); 82 | 83 | // variable def, won't know this when casting 84 | case VARIABLE_DEF: 85 | return obj; 86 | 87 | // instance object 88 | case INSTANCE: 89 | return cast(obj, prototype[1], types, castPrimitives, freeMemory, definitions); 90 | } 91 | 92 | // non-prototyped object 93 | } else if (!obj || !obj.constructor || obj.constructor.prototype == Object.prototype) { 94 | 95 | let tbjson = prototype.tbjson; 96 | 97 | // prototype is tbjson with a definition 98 | if (tbjson && tbjson.definition) { 99 | 100 | let typedObj; 101 | let definition; 102 | 103 | // call the cast function to instantiate the correct prototype 104 | if (tbjson.cast) { 105 | return cast(obj, tbjson.cast(obj), types, castPrimitives, freeMemory, definitions); 106 | 107 | // use the passed prototype 108 | } else { 109 | typedObj = new prototype(); 110 | } 111 | 112 | if (isNonNullObject) { 113 | 114 | // use map 115 | if (definitions[prototype.name]) { 116 | definition = definitions[prototype.name]; 117 | 118 | // check for parent 119 | } else { 120 | 121 | definition = tbjson.definition; 122 | 123 | // only check for a parent if the definition is an object 124 | if (typeof definition == 'object') { 125 | 126 | for (let parent = prototype; parent = getParent(parent);) { 127 | if (!parent.tbjson || !parent.tbjson.definition) { break; } 128 | definition = Object.assign({}, parent.tbjson.definition, definition); 129 | } 130 | 131 | definitions[prototype.name] = definition; 132 | } 133 | } 134 | 135 | // fallback to the prototype if definition is an object 136 | if (definition == OBJECT) { 137 | for (let key in typedObj) { 138 | if (key in obj) { 139 | typedObj[key] = obj[key]; 140 | if (freeMemory) { obj[key] = undefined; } 141 | } 142 | } 143 | 144 | // continue deeper 145 | } else { 146 | for (let key in definition) { 147 | if (key in obj) { 148 | typedObj[key] = cast(obj[key], definition[key], types, castPrimitives, freeMemory, definitions); 149 | if (freeMemory) { obj[key] = undefined; } 150 | } 151 | } 152 | } 153 | } 154 | 155 | // call the build function for post construction 156 | if (tbjson.build) { 157 | tbjson.build(typedObj); 158 | } 159 | 160 | return typedObj; 161 | 162 | // prototype is a raw definition 163 | } else { 164 | 165 | let typedObj = {}; 166 | 167 | if (isNonNullObject) { 168 | for (let key in prototype) { 169 | if (key in obj) { 170 | typedObj[key] = cast(obj[key], prototype[key], types, castPrimitives, freeMemory, definitions); 171 | if (freeMemory) { obj[key] = undefined; } 172 | } 173 | } 174 | } 175 | 176 | return typedObj; 177 | } 178 | } 179 | } 180 | 181 | // cast type from its base64 data 182 | if (types && typeof prototype == 'string' && typeof obj == 'string' && prototype[0] == '@' && types[prototype]) { 183 | obj = types[prototype].deserialize(Buffer.from(obj, 'base64')); 184 | 185 | // cast primitive (allow for null) 186 | } else if (castPrimitives && typeof prototype == 'number' && prototype < ARRAY) { 187 | 188 | if (typeof obj != 'boolean' && typeof obj != 'number' && typeof obj != 'string') { 189 | obj = null; 190 | 191 | // bool 192 | } else if (prototype == BOOL) { 193 | obj = !!obj; 194 | 195 | // string 196 | } else if (prototype == STRING) { 197 | obj = '' + obj; 198 | 199 | // number 200 | } else { 201 | obj = +obj; 202 | } 203 | } 204 | 205 | // primitive, untyped, or prototyped 206 | return obj; 207 | } -------------------------------------------------------------------------------- /src/functions/validate.js: -------------------------------------------------------------------------------- 1 | import { 2 | NULL, 3 | BOOL, 4 | INT8, 5 | UINT8, 6 | INT16, 7 | UINT16, 8 | INT32, 9 | UINT32, 10 | FLOAT32, 11 | FLOAT64, 12 | STRING, 13 | ARRAY, 14 | OBJECT, 15 | NULLABLE, 16 | TYPED_ARRAY, 17 | INSTANCE 18 | } from '../constants'; 19 | 20 | import getParent from '../utility/getParent'; 21 | 22 | /** 23 | * Check a prototype instance (or plain object with specified proto) for invalid fields using the TBJSON definition when available. 24 | * Return a nested object mirroring the source object with errors. 25 | * 26 | * @param { object } obj - object to check 27 | * @param { function } prototype - prototype to to treat object as 28 | * @param { object } options - options 29 | */ 30 | export default function validate(obj, prototype = null, options = {}, definitions = {}, errorCount = 0) { 31 | 32 | let walkObj; 33 | let walkProto; 34 | 35 | // validate type 36 | if (typeof prototype == 'number') { 37 | 38 | if (!validTypedValue(obj, prototype, options)) { 39 | return [prototype, errorCount + 1]; 40 | } 41 | 42 | // compound or recursive check 43 | } else { 44 | 45 | let isArray = Array.isArray(prototype); 46 | let isArrayTypeDef = Array.isArray(prototype) && prototype.length == 2; 47 | 48 | // array 49 | if (isArray && (Array.isArray(obj) || isTypedArray(obj))) { 50 | 51 | // typed array 52 | if (isArrayTypeDef && (prototype[0] == ARRAY || prototype[1] == TYPED_ARRAY)) { 53 | walkObj = obj; 54 | walkProto = prototype[1]; 55 | 56 | // unknown array 57 | } else { 58 | walkObj = prototype; 59 | } 60 | 61 | // qualified type 62 | } else if (isArrayTypeDef) { 63 | 64 | switch (prototype[0]) { 65 | 66 | // uniform value object 67 | case OBJECT: 68 | 69 | // cannot be null 70 | if (typeof obj == 'object' && obj) { 71 | walkObj = obj; 72 | walkProto = prototype[1]; 73 | 74 | // a null object must be marked nullable 75 | } else { 76 | errors = OBJECT; 77 | errorCount++; 78 | } 79 | 80 | break; 81 | 82 | // nullable object 83 | case NULLABLE: 84 | 85 | // ignore if null 86 | if (obj != null) { 87 | 88 | // ignore nullable nan 89 | if (!( 90 | options.allowNullableNaN && 91 | typeof prototype[1] == 'number' && 92 | prototype[1] >= UINT8 && 93 | prototype[1] <= FLOAT64 && 94 | Number.isNaN(obj) 95 | )) { 96 | return validate(obj, prototype[1], options, definitions, errorCount); 97 | } 98 | } 99 | 100 | break; 101 | 102 | // instance object 103 | case INSTANCE: 104 | return validate(obj, prototype[1], options, definitions, errorCount); 105 | } 106 | 107 | // object 108 | } else if (typeof obj =='object' && obj) { 109 | 110 | let definition; 111 | 112 | if (!prototype) { 113 | prototype = obj.constructor; 114 | } 115 | 116 | // prototype is tbjson with a definition 117 | if (typeof prototype == 'function' && prototype.tbjson) { 118 | 119 | // call the validate function for custom validation 120 | if (prototype.tbjson.validate) { 121 | return tbjson.validate(obj, options, errorCount); 122 | 123 | // use the passed prototype 124 | } else { 125 | 126 | // use map 127 | if (definitions[prototype.name]) { 128 | definition = definitions[prototype.name]; 129 | 130 | // check for parent 131 | } else { 132 | 133 | definition = prototype.tbjson.definition; 134 | 135 | // only check for a parent if the definition is an object 136 | if (typeof definition == 'object') { 137 | 138 | for (let parent = prototype; parent = getParent(parent);) { 139 | if (!parent.tbjson || !parent.tbjson.definition) { break; } 140 | definition = Object.assign({}, parent.tbjson.definition, definition); 141 | } 142 | 143 | definitions[prototype.name] = definition; 144 | } 145 | } 146 | } 147 | 148 | // definition object 149 | } else if (typeof prototype == 'object' && prototype) { 150 | definition = prototype; 151 | 152 | // pseudo prototype 153 | } else if (typeof prototype == 'string') { 154 | definition = definitions[prototype]; 155 | } 156 | 157 | if (definition) { 158 | walkObj = definition; 159 | prototype = definition; 160 | } 161 | } 162 | } 163 | 164 | // recurse into the object 165 | if (walkObj) { 166 | 167 | let errors = {}; 168 | let inputErrorCount = errorCount; 169 | 170 | for (let key in walkObj) { 171 | 172 | let [subErrors, subErrorCount] = validate(obj[key], walkProto || prototype[key], options, definitions, errorCount); 173 | 174 | if (subErrorCount > errorCount) { 175 | errors[key] = subErrors; 176 | errorCount = subErrorCount; 177 | } 178 | 179 | if (options.returnOnNthError && errorCount >= options.returnOnNthError) { 180 | break; 181 | } 182 | } 183 | 184 | return [errorCount > inputErrorCount ? errors : null, errorCount]; 185 | 186 | // no errors 187 | } else { 188 | return [null, 0]; 189 | } 190 | } 191 | 192 | /* internal */ 193 | 194 | /** 195 | * Return true if the value passed is a typed array. 196 | * 197 | * @param { * } val - val to check 198 | */ 199 | export function isTypedArray(val) { 200 | if (typeof val == 'object' && val) { 201 | return ( 202 | val instanceof Uint8Array || 203 | val instanceof Int8Array || 204 | val instanceof Uint16Array || 205 | val instanceof Int16Array || 206 | val instanceof Uint32Array || 207 | val instanceof Int32Array || 208 | val instanceof Float32Array || 209 | val instanceof Float64Array 210 | ); 211 | } else { 212 | return false; 213 | } 214 | } 215 | 216 | /** 217 | * Check if a value conforms to a primitive type. 218 | * 219 | * @param { * } val - value to check 220 | * @param { number } type - the tbjson primitive type 221 | * @param { object } options - options 222 | */ 223 | function validTypedValue(val, type, options = {}) { 224 | switch (type) { 225 | 226 | case NULL: 227 | return val == null; 228 | 229 | case BOOL: 230 | return typeof val == 'boolean' || val === 0 || val === 1; 231 | 232 | case INT8: 233 | return typeof val == 'number' && (Number.isNaN(val) ? !!options.allowNaN : val >= -128 && val <= 127); 234 | 235 | case UINT8: 236 | return typeof val == 'number' && (Number.isNaN(val) ? !!options.allowNaN : val >= 0 && val <= 255); 237 | 238 | case INT16: 239 | return typeof val == 'number' && (Number.isNaN(val) ? !!options.allowNaN : val >= -32768 && val <= 32767); 240 | 241 | case UINT16: 242 | return typeof val == 'number' && (Number.isNaN(val) ? !!options.allowNaN : val >= 0 && val <= 65535); 243 | 244 | case INT32: 245 | return typeof val == 'number' && (Number.isNaN(val) ? !!options.allowNaN : val >= -2147483648 && val < 2147483647); 246 | 247 | case UINT32: 248 | return typeof val == 'number' && (Number.isNaN(val) ? !!options.allowNaN : val >= 0 && val <= 4294967295); 249 | 250 | case FLOAT32: 251 | case FLOAT64: 252 | return typeof val == 'number' && (!!options.allowNaN || !Number.isNaN(val)); 253 | 254 | case STRING: 255 | return typeof val == 'string' || (!!options.allowNullString && val == null); 256 | 257 | case ARRAY: 258 | return Array.isArray(val); 259 | 260 | case OBJECT: 261 | return typeof val == 'object' && val != null; 262 | } 263 | 264 | return true; 265 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typed Binary JSON 2 | 3 | Typed Binary JSON or TBJSON, is a binary serialization format that is compatible with JSON. 4 | It stores known object prototypes in a JSON header, and serializes the data in a binary format following the header. 5 | 6 | TBJSON is useful for serializing known objects, classes, or types, otherwise it will offer little advantage if any in terms of size or performance over JSON. 7 | 8 | For a browser compatible version of this package, use [TBJSON in the Browser](https://www.npmjs.com/package/typed-binary-json-browser). 9 | 10 | ## Format 11 | 12 | ### File Format 13 | 14 | Each file starts off with `.tbj` to singinify that it is a `Typed Binary JSON` file, followed by a `unit32` which is the length of the header. 15 | ``` 16 | length of header raw binary data 17 | .tbj header in JSON 18 | . t b j [ uint32 ] { . . . } . . d a t a . . 19 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 20 | 0 10 20 21 | ``` 22 | 23 | Offset | Value | Meaning 24 | --- | --- | --- 25 | 0 | .tbj | States the file type. 26 | 4 | uint32 | Size of the JSON header. 27 | 8 | JSON | A utf-8 serialized JSON map of the binary data to follow. 28 | x | binary | The binary data. Always the next byte after the last header byte. 29 | 30 | ### Header Format 31 | 32 | The header contains information necessary to parse the binary data. It is raw `JSON` and makes it easy to peak at the file and see how the data is structured. 33 | 34 | Entry | Meaning 35 | --- | --- 36 | typeRefs | A map that translates known type names to their codes. 37 | types | Custom primitive types that have been defined for this serialization. 38 | protoRefs | A map that translates known class and object names (either passed in or the object's constructor name) to their codes. 39 | protos | Definitions for known prototypes or classes that are referenced in the root definition. 40 | objs | Definitions for unknown objects that are referenced in known prototypes. 41 | root | The object that was serialized. Contains the definition needed to decode the binary format. 42 | 43 | ### Types 44 | 45 | The types used by TBJSON. 46 | 47 | Type | Code | Definition 48 | --- | --- | --- 49 | Primitives | - | - 50 | NULL | 0 | Null value. 51 | BOOL | 1 | Boolean. 52 | UINT8 | 2 | 8 bit unsigned integer. 53 | INT8 | 3 | 8 bit signed integer. 54 | UINT16 | 4 | 16 bit unsigned integer. 55 | INT16 | 5 | 16 bit signed integer. 56 | UINT32 | 6 | 32 bit unsigned integer. 57 | INT32 | 7 | 32 bit signed integer. 58 | FLOAT32 | 8 | 32 bit floating point. 59 | FLOAT64 | 9 | 64 bit double precision floating point. 60 | Complex Types | - | - 61 | STRING | 10 | String. 62 | ARRAY | 11 | Array. Used as `Tbjson.TYPES.ARRAY` or `[Tbjson.TYPES.ARRAY, ]`. Like: `[Tbjson.TYPES.ARRAY, Tbjson.TYPES.FLOAT32]`. 63 | OBJECT | 12 | Object. Used as `Tbjson.TYPES.OBJECT` or `[Tbjson.TYPES.OBJECT, ]` if all the values in the object are the same type. Like: `[Tbjson.TYPES.OBJECT, MyClass]`. 64 | NULLABLE | 13 | Nullable value. Used as `[Tbjson.TYPES.NULLABLE, ]`. Like: `[Tbjson.TYPES.NULLABLE, Tbjson.TYPES.STRING]`. 65 | TYPED_ARRAY | 14 | Typed array. Used as `Float32Array` or `Int16Array`. Used like `[Tbjson.TYPES.TYPED_ARRAY, `. Like: `[Tbjson.TYPES.TYPED_ARRAY, Tbjson.TYPES.INT32]`. 66 | UNKNOWN | 15 | Unknown type. Wildcard that can represent a JS number, boolean, or string. 67 | Extras | - | - 68 | VARIABLE_DEF | 16 | A variable definition. That is a definition that is not yet known, but will be known just before serialization. Registered by `tbjson.registerVariableDef('var1', { ... })`. Used as `Tbjson.TYPES.VARIABLE_DEF`. 69 | INSTANCE | 17 | An instance of a class. Useful for subclasses. Like `[Tbjson.TYPES.INSTANCE, MySuperClass]`. 70 | 71 | ### Reference 72 | 73 | ```js 74 | // use an import 75 | import Tbjson from 'typed-binary-json'; 76 | // or require 77 | const Tbjson = require('typed-binary-json'); 78 | 79 | // make a new instance 80 | let tbjson = new Tbjson(); 81 | 82 | // serialize a plain object to a buffer 83 | let serializedToBuffer = tbjson.serializeToBuffer({ a: 'a', b: 1, c: true }); 84 | 85 | // buffer looks like: 86 | // 87 | // byte offset: data 88 | // 89 | // 000: .tbj 90 | // 004: (uint32)12 91 | // 008: { 92 | // "version": 1, 93 | // "offsets": { 94 | // "prototype": 64, 95 | // "nullablePrototype": 256, 96 | // "array": 512, 97 | // "object": 4096 98 | // }, 99 | // "typeRefs": {}, 100 | // "typeDefs": {}, 101 | // "protoRefs": {}, 102 | // "protoDefs": {}, 103 | // "objs": {}, 104 | // "root": { 105 | // "a": 10, 106 | // "b": 9, 107 | // "c": 1 108 | // } 109 | // } 110 | // 194: binary data 111 | 112 | // parse a buffer (deserialize) 113 | tbjson.parseBuffer(serializedToBuffer); 114 | 115 | class Test { 116 | constructor() { 117 | this.x = [0, 1, 2, 3, 4, 5, 6, 7, 8]; 118 | } 119 | } 120 | Test.tbjson = { 121 | definition: { 122 | x: [Tbjson.TYPES.ARRAY, Tbjson.TYPES.UINT32] 123 | } 124 | }; 125 | 126 | // serialize a prototyped object to a buffer 127 | serializedToBuffer = tbjson.serializeToBuffer(new Test()); 128 | 129 | // buffer looks like: 130 | // 131 | // byte offset: data 132 | // 133 | // 000: .tbj 134 | // 004: (uint32)12 135 | // 008: { 136 | // "version": 1, 137 | // "offsets": { 138 | // "prototype": 64, 139 | // "nullablePrototype": 256, 140 | // "array": 512, 141 | // "object": 4096 142 | // }, 143 | // "typeRefs": {}, 144 | // "typeDefs": {}, 145 | // "protoRefs": { 146 | // "Test": 64 147 | // }, 148 | // "protoDefs": { 149 | // "64": { 150 | // "x": 518 151 | // } 152 | // }, 153 | // "objs": {}, 154 | // "root": 64 155 | // } 156 | // 199: binary data 157 | 158 | ``` 159 | 160 | ### Working Example 161 | 162 | Refer to the `test` dir to see all possible examples. 163 | 164 | ```js 165 | import Tbjson from 'typed-binary-json'; 166 | 167 | class A { 168 | x = 0; 169 | y = 0; 170 | z = 0; 171 | } 172 | 173 | // make A a known prototype 174 | A.tbjson = { 175 | definition: { 176 | x: Tbjson.TYPES.FLOAT32, 177 | y: Tbjson.TYPES.FLOAT32, 178 | z: Tbjson.TYPES.FLOAT32, 179 | } 180 | }; 181 | 182 | class B { 183 | as = [new A()]; 184 | string = 'string'; 185 | bool = false; 186 | number = 100.5; 187 | } 188 | 189 | // make B a known prototype 190 | B.tbjson = { 191 | definition: { 192 | // use the [ array, type ] notation to say that "B.as" is an array of A 193 | as: [Tbjson.TYPES.ARRAY, A], 194 | string: Tbjson.TYPES.STRING, 195 | bool: Tbjson.TYPES.BOOL, 196 | number: Tbjson.TYPES.FLOAT64 197 | } 198 | } 199 | 200 | // make a root object (untyped) 201 | let root = { 202 | b: new B() 203 | }; 204 | 205 | (async function() { 206 | 207 | let tbjson = new Tbjson(); 208 | 209 | // serialize to a file 210 | await tbjson.serializeToFile('test.tbj', root); 211 | 212 | // parse from a file 213 | let obj = tbjson.parseFileAsBuffer('test.tbj'); 214 | 215 | console.log(obj); 216 | })(); 217 | ``` 218 | 219 | ## Methods 220 | 221 | ### Serialization 222 | 223 | **serializeToBuffer(obj)** 224 | 225 | Serialize `obj`. Create a buffer and write to it. 226 | 227 | **serializeToStream(stream, obj)** 228 | 229 | Serialize `obj`. Write to `stream`. 230 | 231 | **serializeToFile(filename, obj)** 232 | 233 | Serialize `obj`. Create a write stream for `filename` and write out to the file stream. 234 | 235 | ### Parsing 236 | 237 | **parseBuffer(buffer)** 238 | 239 | Parse the `buffer`. Return the parsed object. 240 | 241 | **parseStream(stream)** 242 | 243 | Parse the `stream`. Return the parsed object. 244 | 245 | **parseFileAsBuffer(filename)** 246 | 247 | Read the file `filename` into memory and parse its conents. Preferred for performance. Return the parsed object. 248 | 249 | **parseFileAsStream(filename)** 250 | 251 | Create a read stream for `filename` and parse its contents. Useful for very large files, but slower. Return the parsed object. 252 | 253 | ### Registrations 254 | 255 | Most of these functions are not necessary to call if `tbjson` is set on the class level. 256 | 257 | **finalizePrototypes()** 258 | 259 | *See `test/inheritance` for an example.* 260 | 261 | Must be called if inheritance or referenced definitions are used or registered. 262 | 263 | ```js 264 | let tbson = new Tbjson(); 265 | tbjson.registerPrototype({ 266 | reference: 'X', 267 | definition: { 268 | x: Tbjson.TYPES.STRING 269 | } 270 | }); 271 | ``` 272 | 273 | **registerPrototype(obj)** 274 | 275 | *See `test/type` and `test/inheritance` for examples.* 276 | 277 | *Not needed if tbjson is set statically on a class.* 278 | 279 | Register a prototype. `obj` is the definition for the prototype. 280 | 281 | ```js 282 | let tbson = new Tbjson(); 283 | tbjson.registerPrototype({ 284 | prototype: X, 285 | definition: { 286 | x: Tbjson.TYPES.STRING 287 | } 288 | }); 289 | ``` 290 | 291 | **registerPrototypes(array)** 292 | 293 | Register an array of prototypes. `array` is an array of prototypes. 294 | 295 | **registerPseudoPrototype(id, def)** 296 | 297 | Register a pseudo prototype. That is a plain (non-prototyped) object that will have a known structure just before serialization. 298 | `id` is a number of string to identify the definition. `ref` is the definition. Can be qualified, like marked `nullable`. 299 | 300 | ```js 301 | let tbjson = new Tbjson(); 302 | tbjson.registerPseudoPrototype('x', { 303 | x: Tbjson.TYPES.STRING 304 | }); 305 | 306 | class X { 307 | constructor() { 308 | this.x = null; 309 | } 310 | } 311 | X.tbjson = { 312 | definition: { 313 | x: [Tbjson.TYPES.NULLABLE, 'x'] 314 | } 315 | }; 316 | ``` 317 | 318 | **registerVariableDef(id, def)** 319 | 320 | Register a variable definition. That is a plain (non-prototyped) object that will have a known structure just before serialization. `id` is a number or string to identify the definition. `ref` is the definition. Unlike a pseduo prototype, this cannot be qualified - like marked `nullable`. 321 | 322 | ```js 323 | let tbjson = new Tbjson(); 324 | tbjson.registerVariableDef('x', { 325 | x: Tbjson.TYPES.STRING 326 | }); 327 | 328 | class X { 329 | constructor() { 330 | this.x = { 331 | x: 'x' 332 | }; 333 | } 334 | } 335 | X.tbjson = { 336 | definition: { 337 | x: [Tbjson.TYPES.VARIABLE_DEF, 'x'] 338 | } 339 | }; 340 | ``` 341 | 342 | **registerType(type)** 343 | 344 | Not available yet. Register a custom type (a primitive like `int48`, etc...). `type` is the definition for the custom type. 345 | 346 | ### Static 347 | 348 | **cast(obj, prototype)** 349 | 350 | *See `test/cast` and `test/protoypeCast` for examples.* 351 | 352 | Cast the given `obj` as `prototype`. 353 | 354 | ```js 355 | class X {} 356 | X.tbjson = { 357 | definition: { 358 | x: Tbjson.TYPES.STRING 359 | } 360 | }; 361 | Tbjson.cast({ x: 'x' }, X); 362 | ``` 363 | 364 | **clone(obj)** 365 | 366 | *See `test/clone` for an example.* 367 | 368 | Clone the `obj` into a prototyped object ignoring typing rules, but obeying which properties should be ignored. 369 | 370 | ```js 371 | class X {} 372 | let cloneX = Tbjson.clone(x); 373 | ``` 374 | 375 | **definition(obj)** 376 | 377 | *See `test/definition` for an example.* 378 | 379 | Helper function to extract the definition (including parent prototypes) of `obj`. 380 | 381 | ```js 382 | class Y {} 383 | y.tbjson = { 384 | definition: { 385 | a: Tbjson.TYPES.STRING 386 | } 387 | }; 388 | class X extends Y {} 389 | X.tbjson = { 390 | definition: { 391 | b: Tbjson.TYPES.STRING 392 | } 393 | }; 394 | 395 | Tbjson.definition(x); 396 | // { a: 'a', b: 'b' } 397 | ``` 398 | 399 | **serialize(obj)** 400 | 401 | *See `test/serialize` for an example.* 402 | 403 | Serialize `obj` into a plain object ignoring typings, but obeying which properties should be ignored. 404 | 405 | ```js 406 | class X { 407 | constriuctor() { 408 | this.a = 'a'; 409 | this.b = 'b'; 410 | } 411 | } 412 | X.tbjson = { 413 | definition: { 414 | a: Tbjson.TYPES.STRING 415 | } 416 | }; 417 | 418 | let x = new X(); 419 | 420 | Tbjson.serialize(x); 421 | // x.b is ignored because it is not part of the tbjson definition 422 | // { a: 'a' } 423 | ``` 424 | 425 | ## Performance 426 | 427 | Performance varies on the data type, but you'll get best performance if your types have lots of numeric values, and even better performance if you can take advantage of `float32`, `int32`, `int16`, and `int8` to save space. 428 | 429 | 100 of `root.first` 430 | 10K per each `root.first` of `first.second` 431 | 432 | ```json 433 | { 434 | "root": { 435 | "first": [{ 436 | "second": [{ 437 | "x": 100000.666666666666, 438 | "y": -999999.999, 439 | "z": 1234.5678901234, 440 | "details": { 441 | "alpha": "oranges", 442 | "beta": 10, 443 | "gamma": [-3.14159, false, true, "!@#$%^&*()"] 444 | } 445 | }], 446 | "anotherString": "apples", 447 | "number": 86, 448 | "bool": true, 449 | "array": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 450 | }] 451 | } 452 | } 453 | ``` 454 | 455 | Benchmark | Filesize | Time 456 | --- | --- | --- 457 | JSON Write | 140 MB | 2,648 ms 458 | TBJSON Write | 37 MB | 1,154 ms 459 | JSON Read | N/A | 2,073 ms 460 | TBJSON Read | N/A | 1,453 ms 461 | 462 | ## Contributing 463 | 464 | Feel free to make changes and submit pull requests whenever. 465 | 466 | ## License 467 | 468 | Typed Binary JSON uses the [MIT](https://opensource.org/licenses/MIT) license. -------------------------------------------------------------------------------- /src/Tbjson.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import { 4 | MAGIC_NUMBER, 5 | SIZE_MAGIC_NUMBER, 6 | VERSION, 7 | ERROR, 8 | 9 | NULL, 10 | BOOL, 11 | INT8, 12 | UINT8, 13 | INT16, 14 | UINT16, 15 | INT32, 16 | UINT32, 17 | FLOAT32, 18 | FLOAT64, 19 | STRING, 20 | ARRAY, 21 | OBJECT, 22 | NULLABLE, 23 | TYPED_ARRAY, 24 | UNKNOWN, 25 | VARIABLE_DEF, 26 | INSTANCE, 27 | 28 | SIZE_UINT32, 29 | 30 | NULLABLE_OFFSET, 31 | TYPED_ARRAY_OFFSET, 32 | TYPE_OFFSET, 33 | PROTOTYPE_OFFSET, 34 | NULLABLE_PROTOTYPE_OFFSET, 35 | ARRAY_OFFSET, 36 | OBJECT_OFFSET, 37 | 38 | L_NULLABLE_PROTOTYPE_OFFSET, 39 | L_ARRAY_OFFSET, 40 | L_OBJECT_OFFSET, 41 | 42 | DEFAULT_BUFFER_SIZE, 43 | DEFAULT_NUM_ENCODING, 44 | DEFAULT_STR_ENCODING, 45 | DEFAULT_X_FACTOR 46 | } from './constants'; 47 | 48 | import Prototype from './Prototype'; 49 | 50 | import BufferReader from './utility/BufferReader'; 51 | import BufferWriter from './utility/BufferWriter'; 52 | import StreamBufferReader from './utility/StreamBufferReader'; 53 | import StreamBufferWriter from './utility/StreamBufferWriter'; 54 | import getParent from './utility/getParent'; 55 | 56 | import Type from './types/Type'; 57 | import BigIntType from './types/BigIntType'; 58 | import DateType from './types/DateType'; 59 | import RegexType from './types/RegexType'; 60 | 61 | import cast from './functions/cast'; 62 | import clone from './functions/clone'; 63 | import definition from './functions/definition'; 64 | import flattenValidation from './functions/flattenValidation'; 65 | import serialize from './functions/serialize'; 66 | import validate from './functions/validate'; 67 | 68 | /** 69 | * Tbjson 70 | * 71 | * A JS TBJSON serializer and parser. 72 | */ 73 | export default class Tbjson { 74 | 75 | version = VERSION; 76 | 77 | // for registered types (primitives) 78 | typeRefs = {}; 79 | types = {}; 80 | 81 | // for registered prototypes (classes) 82 | protoRefs = {}; 83 | protos = {}; 84 | 85 | // for plain objects that are inside of known prototypers 86 | objs = {}; 87 | 88 | // for variable definitions 89 | variableDefs = {}; 90 | 91 | // binary definition tree 92 | root = null; 93 | 94 | // counters for converting types and prototypes to an incrementing numeric value 95 | nextObjCode = 0; 96 | nextTypeCode = TYPE_OFFSET; 97 | nextProtoCode; 98 | 99 | finalized = false; 100 | 101 | // default offsets 102 | offsets = { 103 | prototype: PROTOTYPE_OFFSET, 104 | nullablePrototype: NULLABLE_PROTOTYPE_OFFSET, 105 | array: ARRAY_OFFSET, 106 | object: OBJECT_OFFSET 107 | }; 108 | 109 | // default options 110 | options = { 111 | bufferSize: DEFAULT_BUFFER_SIZE, 112 | numEncoding: DEFAULT_NUM_ENCODING, 113 | strEncoding: DEFAULT_STR_ENCODING, 114 | xFactor: DEFAULT_X_FACTOR 115 | }; 116 | 117 | constructor(types = [], prototypes = [], offsets = {}, options = {}) { 118 | 119 | this.offsets = { 120 | ...this.offsets, 121 | ...offsets 122 | }; 123 | 124 | this.options = { 125 | ...this.options, 126 | ...options 127 | }; 128 | 129 | this.nextProtoCode = this.offsets.prototype; 130 | 131 | this.registerTypes(types); 132 | this.registerPrototypes(prototypes); 133 | } 134 | 135 | /*-----------------------------------------------------------------------*/ 136 | /* registers */ 137 | 138 | /** 139 | * Register a variable definition so that any prototypes with the same variable definition id are replaced before serializing. 140 | * 141 | * @param { number | string } id - the identifier of this variable definition 142 | * @param { obj } def - the definition to set to 143 | */ 144 | registerVariableDef(id, def) { 145 | 146 | // format the definition 147 | def = def ? this.fmtDef(def) : null; 148 | if (def == ERROR) { 149 | throw new Error(`Invalid definition for variable: ${id}`); 150 | } 151 | 152 | this.variableDefs[id] = def; 153 | } 154 | 155 | /** 156 | * Register a pseudo prototype, rather a variable definition that should be treated as if it were a prototype (to support nullable, object, and array). 157 | * 158 | * @param { number | string } id - the identifier of this pseduo prototype 159 | * @param { obj } def - the definition to set to 160 | */ 161 | registerPseudoPrototype(id, def) { 162 | 163 | let code = this.protoRefs[id]; 164 | 165 | // already registered 166 | if (code && this.protos[this.protoRefs[id]]) { 167 | return; 168 | } 169 | 170 | // format the definition 171 | def = def ? this.fmtDef(def) : null; 172 | if (def == ERROR) { 173 | throw new Error(`Invalid definition for variable: ${id}`); 174 | } 175 | 176 | // set the code if not already set 177 | if (!code) { 178 | code = this.nextProtoCode++; 179 | this.protoRefs[id] = code; 180 | } 181 | 182 | this.protos[code] = new Prototype(def); 183 | } 184 | 185 | /** 186 | * Register a prototype / class or plain objecct for serilization and deserialization. 187 | * If using Class.tbjson = { ... } you must call this for each class, and then call finalizePrototypes for inheritance to work. 188 | * 189 | * Example: 190 | * 191 | * Tbjson.registerPrototype(Point); // point must have tbjson set on it: Point.tbjson = { definition: ... } 192 | * 193 | * Tbjson.registerPrototype({ 194 | * prototype: Point1, 195 | * definition: { 196 | * x: Tbjson.TYPES.FLOAT32, 197 | * y: Tbjson.TYPES.FLOAT32 198 | * }, 199 | * reference: 'Point', 200 | * parentReference: Point0 201 | * }); 202 | * 203 | * Tbjson.registerPrototype({ 204 | * reference: Point, 205 | * definition: { 206 | * x: Tbjson.TYPES.FLOAT32, 207 | * y: Tbjson.TYPES.FLOAT32 208 | * }); 209 | * 210 | * @param { function | object } prototype - class / prototype constructor or a plain object that represents one 211 | */ 212 | registerPrototype(prototype) { 213 | 214 | // check if finalized 215 | if (this.finalized) { 216 | 217 | if (typeof prototype == 'function' && prototype.tbjson) { 218 | return this.protoRefs[prototype.name]; 219 | } 220 | 221 | return; 222 | } 223 | 224 | // a prototype 225 | if (typeof prototype == 'function') { 226 | 227 | // check if it's a known tbjson prototype 228 | if (prototype.tbjson) { 229 | 230 | // TODO: REMOVE THIS 231 | if (!prototype.tbjson.definition) { 232 | throw new Error(`Missing definition for "${prototype.name}"`); 233 | } 234 | 235 | prototype = { 236 | prototype: prototype, 237 | ...prototype.tbjson 238 | }; 239 | 240 | } else { 241 | prototype = { prototype }; 242 | } 243 | } 244 | 245 | // if the ref is not set, use the name 246 | if (!prototype.reference) { 247 | prototype.reference = prototype.prototype.name; 248 | } 249 | 250 | let code = this.protoRefs[prototype.reference]; 251 | 252 | // assign a new reference and definition 253 | if (!code) { 254 | code = this.nextProtoCode++; 255 | this.protoRefs[prototype.reference] = code; 256 | } 257 | 258 | // this code has not been defined 259 | if (!this.protos[code] || !this.protos[code].definition) { 260 | 261 | let parentCode; 262 | 263 | // get the parent code 264 | if (prototype.definition) { 265 | let parent = (!prototype.noInherit && prototype.parentReference) ? prototype.parentReference : getParent(prototype.prototype); 266 | parentCode = parent ? this.registerPrototype(parent) : null; 267 | } 268 | 269 | // format the definition 270 | let definition = prototype.definition ? this.fmtDef(prototype.definition) : null; 271 | if (definition == ERROR) { 272 | throw new Error(`Invalid definition for: ${prototype.prototype.name}`); 273 | } 274 | 275 | // set the prototype 276 | this.protos[code] = new Prototype(definition, prototype.prototype, parentCode, prototype.noInherit); 277 | } 278 | 279 | return code; 280 | } 281 | 282 | /** 283 | * Register an array of prototypes. 284 | * 285 | * Example: 286 | * 287 | * [{ 288 | * constructor: Point, 289 | * definition: { 290 | * x: Tbjson.TYPES.FLOAT32, 291 | * y: Tbjson.TYPES.FLOAT32, 292 | * z: Tbjson.TYPES.FLOAT32 293 | * } 294 | * }, { 295 | * constructor: Line, 296 | * reference: 'Line2', 297 | * parentReference: 'Line1', 298 | * noInherit: true, 299 | * definition: { 300 | * point1: 'Point', 301 | * point2: 'Point' 302 | * } 303 | * }] 304 | * 305 | * @param { []object } prototypes - array of prototypes 306 | */ 307 | registerPrototypes(prototypes = []) { 308 | for (let prototype of prototypes) { 309 | this.registerPrototype(prototype); 310 | } 311 | } 312 | 313 | /** 314 | * Register a type. 315 | * 316 | * Example: 317 | * 318 | * { 319 | * ref: 'Float48', 320 | * serialize: (buffer, data) => { ... }, 321 | * deserialize: (buffer) => { ... } 322 | * } 323 | * 324 | * @param { object } type - type to add 325 | */ 326 | registerType(type) { 327 | 328 | let code = this.typeRefs[type.ref]; 329 | 330 | if (!code) { 331 | 332 | code = this.nextTypeCode++; 333 | 334 | this.typeRefs[type.ref] = code; 335 | this.types[code] = type; 336 | } 337 | 338 | return code; 339 | } 340 | 341 | /** 342 | * Register types. 343 | * 344 | * Example: 345 | * 346 | * [{ 347 | * ref: 'Float48', 348 | * serializer: function(buffer, data) { ... }, 349 | * deserializer: function(buffer) { ... } 350 | * }] 351 | * 352 | * @param { []object } types - array of types to register 353 | */ 354 | registerTypes(types = []) { 355 | for (let type of types) { 356 | this.registerType(type); 357 | } 358 | } 359 | 360 | /** 361 | * If using inheritance, this must be called before serialization to update definitions. 362 | */ 363 | finalizePrototypes() { 364 | 365 | let finalizedProtos = {}; 366 | 367 | while (Object.keys(finalizedProtos).length < Object.keys(this.protos).length) { 368 | for (let code in this.protos) { 369 | 370 | // don't run on finalized prototypes 371 | if (finalizedProtos[code]) { continue; } 372 | 373 | let prototype = this.protos[code]; 374 | 375 | // finalize if there is no parent code or if the prototype is set to not inherit 376 | if (!prototype.parentCode || prototype.noInherit) { 377 | finalizedProtos[code] = true; 378 | continue; 379 | } 380 | 381 | // throw an error if a parent code is missing 382 | if (!this.protos[prototype.parentCode]) { 383 | throw new Error('Missing a parent prototype or definition'); 384 | } 385 | 386 | // parent is finalized, so this can be to 387 | if (finalizedProtos[prototype.parentCode]) { 388 | 389 | // if the definition isn't an object, just use it and ignore any parent definitions 390 | if (typeof prototype.definition == 'object') { 391 | prototype.definition = Object.assign({}, this.protos[prototype.parentCode].definition, prototype.definition); 392 | } 393 | 394 | finalizedProtos[code] = true; 395 | } 396 | } 397 | } 398 | 399 | this.finalized = true; 400 | } 401 | 402 | /*-----------------------------------------------------------------------*/ 403 | /* serializers */ 404 | 405 | /** 406 | * Serialize the obj to a buffer. Fastest, but uses the most memory. 407 | * 408 | * @param { object } obj - object to serialize 409 | */ 410 | serializeToBuffer(obj) { 411 | try { 412 | 413 | this.processVariableDefs(); 414 | 415 | // make a writer 416 | this.writer = new BufferWriter(this.options.bufferSize, this.options.xFactor, this.options.strEncoding); 417 | 418 | // process the obj 419 | this.root = this.serialize(obj); 420 | 421 | // add the header to the front 422 | return Buffer.concat([this.getHeaderAsBuffer(), this.writer.getBuffer()]); 423 | 424 | } catch(e) { 425 | e.message = 'Tbjson failed to serialize to the buffer: ' + e.message; 426 | throw e; 427 | } 428 | } 429 | 430 | /** 431 | * Serialize the object to the stream. Slower, but uses the least memory. 432 | * 433 | * @param { stream } stream - stream to serialize to 434 | * @param { object } obj - object to serialize 435 | */ 436 | serializeToStream(stream, obj) { 437 | try { 438 | 439 | this.processVariableDefs(); 440 | 441 | // make a writer 442 | this.writer = new StreamBufferWriter(stream, this.options.bufferSize, this.options.xFactor, this.options.strEncoding); 443 | 444 | // process the obj 445 | this.root = this.serialize(obj); 446 | 447 | // flush and cleanup 448 | this.writer.flush(); 449 | this.writer = null; 450 | 451 | } catch (e) { 452 | e.message = 'Tbjson failed to serialize to the stream: ' + e.message; 453 | throw e; 454 | } 455 | } 456 | 457 | /** 458 | * Serialize the object to a file. Opens as a write stream, so it's slower and uses less memory. 459 | * 460 | * @param { string } filename - filename / path to write to 461 | * @param { object } obj - object to serialize 462 | */ 463 | serializeToFile(filename, obj) { 464 | return new Promise((res, rej) => { 465 | try { 466 | 467 | this.processVariableDefs(); 468 | 469 | let tempFilename = `${filename}.tmp`; 470 | 471 | // write the data to a tmp file 472 | let writeStream = fs.createWriteStream(tempFilename, 'binary'); 473 | this.serializeToStream(writeStream, obj); 474 | writeStream.end(); 475 | 476 | // write the final file 477 | writeStream = fs.createWriteStream(filename, 'binary'); 478 | 479 | // write the header 480 | writeStream.write(this.getHeaderAsBuffer()); 481 | 482 | // pipe the tmp file to the final file 483 | let readStream = fs.createReadStream(tempFilename, 'binary'); 484 | readStream.pipe(writeStream); 485 | 486 | readStream.on('end', () => { 487 | 488 | // cleanup 489 | fs.unlinkSync(tempFilename); 490 | 491 | res(); 492 | }); 493 | } catch (e) { 494 | e.message = `Tbjson Failed to serialize object to "${filename}": ` + e.message; 495 | rej(e); 496 | } 497 | }); 498 | } 499 | 500 | /*-----------------------------------------------------------------------*/ 501 | /* parsers */ 502 | 503 | /** 504 | * Parse a TBJSON containing buffer into ab object. Fastest, but uses the most memory. 505 | * 506 | * @param { buffer } buffer - buffer to read from 507 | * @param { array } selector - anarray that indicates the selected object path 508 | */ 509 | parseBuffer(buffer, selector = null) { 510 | try { 511 | 512 | if (!buffer) { 513 | throw new Error('Null buffer passed in'); 514 | } 515 | 516 | this.reader = new BufferReader(buffer, this.options.strEncoding); 517 | 518 | // validate the buffer type 519 | if (this.reader.readFixedLengthString(SIZE_MAGIC_NUMBER) != MAGIC_NUMBER) { 520 | throw new Error('Buffer is not a Typed Binary JSON format'); 521 | } 522 | 523 | // get the header length 524 | let headerLength = this.reader.read(UINT32); 525 | 526 | // read and parse the header 527 | this.parseHeader(this.reader.readFixedLengthString(headerLength)); 528 | 529 | // construct the object 530 | if (selector) { 531 | return this.parseAtSelection(this.root, selector); 532 | } else { 533 | return this.parse(this.root); 534 | } 535 | 536 | } catch(e) { 537 | e.message = 'Tbjson failed to parse the buffer: ' + e.message; 538 | throw e; 539 | } 540 | } 541 | 542 | /** 543 | * TODO 544 | * Parse a TBJSON containing stream into an object. Slower, but uses the least memory. 545 | * 546 | * @param { stream } stream - stream to read from 547 | * @param { array } selector - anarray that indicates the selected object path 548 | */ 549 | parseStream(stream, selector = null) { 550 | return new Promise(async (res, rej) => { 551 | 552 | this.reader = new StreamBufferReader(stream); 553 | 554 | // validate the stream type 555 | if (await this.reader.readFixedLengthString(SIZE_MAGIC_NUMBER) != MAGIC_NUMBER) { 556 | rej(new Error('Stream is not a Typed Binary JSON format')); 557 | } 558 | 559 | // get the header length 560 | let headerLength = await this.reader.read(UINT32); 561 | 562 | // read and parse the header 563 | this.parseHeader(await this.reader.readFixedLengthString(headerLength)); 564 | 565 | // construct the object 566 | if (selector) { 567 | res(await this.parseAtSelection(this.root, selector)); 568 | } else { 569 | res(await this.parse(this.root)); 570 | } 571 | }); 572 | } 573 | 574 | /** 575 | * Parse a TBJSON file into the object it represents. Faster, but uses more memory. 576 | * 577 | * @param { string } filename - filename / path to read from 578 | * @param { array } selector - anarray that indicates the selected object path 579 | */ 580 | parseFileAsBuffer(filename, selector = null) { 581 | try { 582 | return this.parseBuffer(fs.readFileSync(filename), selector); 583 | } catch (e) { 584 | e.message = `Tbjson failed to parse "${filename}": ` + e.message; 585 | throw e; 586 | } 587 | } 588 | 589 | /** 590 | * Parse a TBJSON file into the object it represents. Slower, but uses less memory. 591 | * 592 | * @param { string } filename - filename / path to read from 593 | * @param { array } selector - anarray that indicates the selected object path 594 | */ 595 | async parseFileAsStream(filename, selector = null) { 596 | try { 597 | return await this.parseStream(fs.createReadStream(filename), selector); 598 | } catch (e) { 599 | e.message = `Tbjson failed to parse "${filename}": ` + e.message; 600 | throw e; 601 | } 602 | } 603 | 604 | /*-----------------------------------------------------------------------*/ 605 | /* helpers */ 606 | 607 | /** 608 | * Get the header object after serialization. 609 | * Useful if you are writing your custom own stream. 610 | */ 611 | getHeader() { 612 | 613 | // get the type serializers / deserializers 614 | let typeSizes = {}; 615 | for (let code in this.types) { 616 | typeSizes[code] = this.types[code].size; 617 | } 618 | 619 | // get the prototype definitions 620 | let protoDefs = {}; 621 | for (let code in this.protos) { 622 | protoDefs[code] = this.protos[code].definition ? this.protos[code].definition : null; 623 | } 624 | 625 | return { 626 | version: VERSION, 627 | offsets: this.offsets, 628 | typeRefs: this.typeRefs, 629 | typeSizes: typeSizes, 630 | protoRefs: this.protoRefs, 631 | protoDefs: protoDefs, 632 | objs: this.objs, 633 | root: this.root 634 | }; 635 | } 636 | 637 | /** 638 | * Get the header object as a buffer. 639 | * Useful if you are writing your custom format. 640 | */ 641 | getHeaderAsBuffer() { 642 | try { 643 | 644 | // header string 645 | let headerStr = JSON.stringify(this.getHeader()); 646 | 647 | // make a new buffer, add the header, append the binary 648 | let buffer = new BufferWriter(SIZE_MAGIC_NUMBER + SIZE_UINT32 + headerStr.length); 649 | 650 | // str - magic number 651 | buffer.writeFixedLengthString(MAGIC_NUMBER); 652 | 653 | // uint32 - header length 654 | buffer.write(UINT32, Buffer.byteLength(headerStr, this.strEncoding)); 655 | 656 | // str - header 657 | buffer.writeFixedLengthString(headerStr); 658 | 659 | return buffer.getBuffer(); 660 | 661 | } catch (e) { 662 | e.message = 'Tbjson failed to create a buffer for the header: ' + e.message; 663 | throw e; 664 | } 665 | } 666 | 667 | /** 668 | * Parse a TBJSON header from a string. 669 | * Useful if you are writing your own deserializer. 670 | * 671 | * @param { string } headerStr - string containing the encoded JSON header 672 | */ 673 | parseHeader(headerStr) { 674 | try { 675 | 676 | let header = JSON.parse(headerStr); 677 | 678 | this.version = header.version || 0; 679 | 680 | // types 681 | this.typeRefs = header.typeRefs; 682 | for (let code in header.typeSizes) { 683 | if (this.types[code]) { 684 | this.types[code].size = header.typeSizes[code]; 685 | } else { 686 | this.types[code] = new Type(undefined, header.typeSizes[code]); 687 | } 688 | } 689 | 690 | // prototypes (preserve proto constructors for typed parsing) 691 | this.protoRefs = header.protoRefs; 692 | for (let code in header.protoDefs) { 693 | if (this.protos[code]) { 694 | this.protos[code].definition = header.protoDefs[code]; 695 | } else { 696 | this.protos[code] = new Prototype(header.protoDefs[code]); 697 | } 698 | } 699 | 700 | // unknown objects 701 | this.objs = header.objs; 702 | 703 | // set the root 704 | this.root = header.root; 705 | 706 | // offsets 707 | if (header.offsets) { 708 | this.offsets = header.offsets; 709 | 710 | // legacy file, use old offsets 711 | } else { 712 | this.offsets = { 713 | prototype: PROTOTYPE_OFFSET, 714 | nullablePrototype: L_NULLABLE_PROTOTYPE_OFFSET, 715 | array: L_ARRAY_OFFSET, 716 | object: L_OBJECT_OFFSET 717 | }; 718 | } 719 | 720 | } catch (e) { 721 | e.message = 'Tbjson failed to parse header string: ' + e.message; 722 | throw e; 723 | } 724 | } 725 | 726 | /*-----------------------------------------------------------------------*/ 727 | /* internal */ 728 | 729 | /** 730 | * Process all prototype definitions and variable definitions. 731 | */ 732 | processVariableDefs() { 733 | for (let code in this.protos) { 734 | if (this.protos[code].definition) { 735 | this.protos[code].definition = this.replaceVariableDefs(this.protos[code].definition); 736 | } 737 | } 738 | } 739 | 740 | /** 741 | * Replace a variable definition with the corresponding registered one. 742 | * 743 | * @param { obj } def - the definition to check and replace 744 | */ 745 | replaceVariableDefs(def) { 746 | if (typeof def == 'object') { 747 | 748 | // an array, could be a variable definition 749 | if (Array.isArray(def)) { 750 | 751 | if (def.length == 2) { 752 | 753 | switch (def[0]) { 754 | 755 | // a variable def 756 | case VARIABLE_DEF: 757 | 758 | // missing a definition, throw an error 759 | if (!this.variableDefs[def[1]]) { 760 | throw new Error(`Unknown variable def: "${def[1]}"`); 761 | } 762 | 763 | return this.variableDefs[def[1]]; 764 | 765 | // another valid tbjson qualifier 766 | case ARRAY: 767 | case TYPED_ARRAY: 768 | case NULLABLE: 769 | case OBJECT: 770 | 771 | def[1] = this.replaceVariableDefs(def[1]); 772 | 773 | return def; 774 | } 775 | } 776 | 777 | // a fixed-length array 778 | for (let i = 0; i < def.length; ++i) { 779 | def[i] = this.replaceVariableDefs(def[i]); 780 | } 781 | 782 | // a definition 783 | } else { 784 | 785 | for (let key in def) { 786 | def[key] = this.replaceVariableDefs(def[key]); 787 | } 788 | } 789 | } 790 | 791 | return def; 792 | } 793 | 794 | /** 795 | * Format the definition to its number representations. 796 | * 797 | * Converts the more verbose array definitions to simpler numeric ones: 798 | * 799 | * [Tbjson.TYPES.ARRAY, Tbjson.TYPES.FLOAT32] -> ARRAY + FLOAT32 = 12 + 9 = 21 800 | * [Tbjson.TYPES.Array, Class] -> ARRAY + NUM_CLASS = 12 + x 801 | * [Tbjson.TYPES.Array, "class"] -> ARRAY + NUM_CLASS = 12 + x 802 | * 803 | * @param { object | array | number } def - the definition specifying how to decode the binary data 804 | */ 805 | fmtDef(def, depth = 0, insideOf = 0) { 806 | 807 | switch (typeof def) { 808 | 809 | // already in number form, just return it 810 | case 'number': 811 | return def; 812 | 813 | // string referencing a prototype, add the string to the reference lookup table 814 | case 'string': 815 | 816 | // type 817 | if (def[0] == '@') { 818 | 819 | if (!this.typeRefs[def]) { 820 | this.typeRefs[def] = this.nextTypeCode++; 821 | } 822 | 823 | return this.typeRefs[def]; 824 | 825 | // proto 826 | } else { 827 | 828 | if (!this.protoRefs[def]) { 829 | this.protoRefs[def] = this.nextProtoCode++; 830 | } 831 | 832 | return this.protoRefs[def]; 833 | } 834 | 835 | // prototype (class) 836 | case 'function': 837 | return this.registerPrototype(def); 838 | 839 | // object or array 840 | case 'object': 841 | 842 | // invalid null 843 | if (!def) { 844 | break; 845 | 846 | // array 847 | } else if (Array.isArray(def)) { 848 | 849 | // typed array 850 | if (def.length == 2 && typeof def[0] == 'number' && def[0] > STRING) { 851 | 852 | // array 853 | if (def[0] == ARRAY) { 854 | return this.offsets.array + this.fmtDef(def[1], depth + 1, ARRAY); 855 | 856 | // nullable 857 | } else if (def[0] == NULLABLE) { 858 | 859 | let subDef = this.fmtDef(def[1], depth + 1, NULLABLE); 860 | 861 | // primitive 862 | if (subDef < NULLABLE_OFFSET) { 863 | return NULLABLE_OFFSET + subDef; 864 | 865 | // type or prototype 866 | } else { 867 | return this.offsets.nullablePrototype + subDef; 868 | } 869 | 870 | // primitive typed array 871 | } else if (def[0] == TYPED_ARRAY) { 872 | return TYPED_ARRAY_OFFSET + this.fmtDef(def[1], depth + 1, TYPED_ARRAY); 873 | 874 | // object 875 | } else if (def[0] == OBJECT) { 876 | return this.offsets.object + this.fmtDef(def[1], depth + 1, OBJECT); 877 | 878 | // variable def 879 | } else if (def[0] == VARIABLE_DEF) { 880 | 881 | // cannot be nested 882 | if (depth) { 883 | throw new Error(`A variable def cannot be nested, try using a pseudo prototype instead: "${def[1]}"`); 884 | } 885 | 886 | return def; 887 | 888 | // instance object 889 | } else if (def[0] == INSTANCE) { 890 | return OBJECT; 891 | } 892 | 893 | // fixed length array 894 | } else { 895 | 896 | let fmtDef = new Array(def.length); 897 | 898 | for (let i = 0; i < def.length; ++i) { 899 | fmtDef[i] = this.fmtDef(def[i], depth + 1); 900 | } 901 | 902 | // inside of an array or object, register def and return matching code 903 | if (insideOf == ARRAY || insideOf == OBJECT) { 904 | 905 | let code = this.nextProtoCode++; 906 | this.protos[code] = new Prototype(fmtDef); 907 | 908 | return code; 909 | 910 | // just return the def 911 | } else { 912 | return fmtDef; 913 | } 914 | } 915 | 916 | // simple object 917 | } else { 918 | 919 | let fmtDef = {}; 920 | 921 | for (let key in def) { 922 | fmtDef[key] = this.fmtDef(def[key], depth + 1); 923 | } 924 | 925 | return fmtDef; 926 | } 927 | 928 | // invalid type 929 | case 'boolean': 930 | break; 931 | } 932 | 933 | // must have an invalid definition 934 | return ERROR; 935 | } 936 | 937 | /** 938 | * Serialize the object based on its definition. Only run for known prototypes. 939 | * 940 | * @param { object } obj - the object to serialize 941 | * @param { object | array | number } def - the definition specifying how to decode the binary data 942 | * @param { bool } isArray - special case for an unknown def that is an array 943 | */ 944 | serializeDef(obj, def, isArray) { 945 | 946 | // no def, could be a known but undefined prototype, or a plain object, kick back to the serializer 947 | if (!def) { 948 | 949 | // write the code 950 | let code = this.nextObjCode++; 951 | this.writer.write(UINT16, code); 952 | 953 | let ref; 954 | 955 | // write the array 956 | if (isArray) { 957 | 958 | ref = new Array(obj.length); 959 | 960 | for (let i = 0; i < obj.length; ++i) { 961 | ref[i] = this.serialize(obj[i]); 962 | } 963 | 964 | // write the obj 965 | } else { 966 | 967 | ref = {}; 968 | 969 | for (let key in obj) { 970 | ref[key] = this.serialize(obj[key]); 971 | } 972 | } 973 | 974 | this.objs[code] = ref; 975 | 976 | return; 977 | } 978 | 979 | switch (typeof def) { 980 | 981 | // typed 982 | case 'number': 983 | 984 | // primitive or higher-order type 985 | if (def < NULLABLE_OFFSET) { 986 | 987 | // an unknown object 988 | if (def == OBJECT) { 989 | this.serializeDef(obj); 990 | 991 | // an unknown array 992 | } else if (def == ARRAY) { 993 | this.serializeDef(obj, null, true); 994 | 995 | // primitive 996 | } else { 997 | this.writer.write(def, obj); 998 | } 999 | 1000 | // nullable primitive or higher-order type 1001 | } else if (def < TYPED_ARRAY_OFFSET) { 1002 | 1003 | if (obj == null) { 1004 | this.writer.write(NULL); 1005 | } else { 1006 | this.writer.write(BOOL, true); 1007 | this.serializeDef(obj, def - NULLABLE_OFFSET); 1008 | } 1009 | 1010 | // primitive typed array 1011 | } else if (def < TYPE_OFFSET) { 1012 | this.writer.write(UINT32, obj.buffer.byteLength); 1013 | this.writer.writeBuffer(Buffer.from(obj.buffer)); 1014 | 1015 | // type 1016 | } else if (def < this.offsets.prototype) { 1017 | 1018 | if (!this.types[def] || this.types[def].ref == null) { 1019 | throw new Error('Missing type definition'); 1020 | } 1021 | 1022 | let buffer = this.types[def].serialize(obj); 1023 | 1024 | if (!this.types[def].size) { 1025 | this.writer.write(UINT16, buffer.length); 1026 | } 1027 | 1028 | this.writer.writeBuffer(buffer); 1029 | 1030 | // prototype 1031 | } else if (def < this.offsets.nullablePrototype) { 1032 | 1033 | if (obj == null || typeof obj != 'object') { 1034 | throw new Error(`Null objects cannot be passed into known prototypes, mark as a nullable known prototype instead: ${this.protos[def] ? this.protos[def].prototype : def}`); 1035 | } 1036 | 1037 | // known prototype 1038 | if (obj.constructor.tbjson) { 1039 | 1040 | // register the prototype if needed 1041 | this.registerPrototype(obj.constructor); 1042 | 1043 | // call the unbuild function for pre serialization 1044 | if (obj.constructor.tbjson.unbuild) { 1045 | obj = obj.constructor.tbjson.unbuild(obj); 1046 | } 1047 | } 1048 | 1049 | this.serializeDef(obj, this.protos[def].definition); 1050 | 1051 | // nullable type or prototype 1052 | } else if (def < this.offsets.array) { 1053 | 1054 | // null values allowed, mark it as null or not 1055 | if (obj == null) { 1056 | this.writer.write(NULL); 1057 | } else { 1058 | this.writer.write(BOOL, true); 1059 | this.serializeDef(obj, def - this.offsets.nullablePrototype); 1060 | } 1061 | 1062 | // variable-length fixed typed array 1063 | } else if (def < this.offsets.object) { 1064 | 1065 | // if valid, continue 1066 | if (obj && Array.isArray(obj)) { 1067 | 1068 | // write out the length 1069 | this.writer.write(UINT32, obj.length); 1070 | 1071 | for (let i = 0; i < obj.length; ++i) { 1072 | this.serializeDef(obj[i], def - this.offsets.array); 1073 | } 1074 | 1075 | // if not valid, auto-cast into an empty array 1076 | } else { 1077 | this.writer.write(UINT32, 0); 1078 | } 1079 | 1080 | // uniform object 1081 | } else { 1082 | 1083 | // if valid, continue 1084 | if (obj && typeof obj == 'object' && !Array.isArray(obj)) { 1085 | 1086 | // write out the length 1087 | this.writer.write(UINT32, Object.keys(obj).length); 1088 | 1089 | // write out the keys and values 1090 | for (let key in obj) { 1091 | this.writer.write(STRING, key); 1092 | this.serializeDef(obj[key], def - this.offsets.object); 1093 | } 1094 | 1095 | // if not valid, auto-cast into an empty object 1096 | } else { 1097 | this.writer.write(UINT32, 0); 1098 | } 1099 | } 1100 | 1101 | break; 1102 | 1103 | // oject or array 1104 | case 'object': 1105 | 1106 | // fixed-length variable type array 1107 | if (Array.isArray(def)) { 1108 | for (let i = 0; i < def.length; ++i) { 1109 | this.serializeDef(obj[i], def[i]); 1110 | } 1111 | 1112 | // object 1113 | } else { 1114 | for (let key in def) { 1115 | this.serializeDef(obj[key], def[key]); 1116 | } 1117 | } 1118 | 1119 | break; 1120 | 1121 | // invalid 1122 | default: 1123 | throw new Error(`Invalid definition: ${def}`); 1124 | } 1125 | } 1126 | 1127 | /** 1128 | * Serialize an object. Can be known (TBJSON has a definition for it) or plain (Class or object that TBJSON doesn't have a definition for). 1129 | * Calls serializeDef() if a known type is found. 1130 | * 1131 | * @param { object } obj - the object to serialize 1132 | */ 1133 | serialize(obj) { 1134 | switch (typeof obj) { 1135 | 1136 | // bool 1137 | case 'boolean': 1138 | this.writer.write(BOOL, obj); 1139 | return BOOL; 1140 | 1141 | // number 1142 | case 'number': 1143 | this.writer.write(FLOAT64, obj); 1144 | return FLOAT64; 1145 | 1146 | // string 1147 | case 'string': 1148 | this.writer.write(STRING, obj); 1149 | return STRING; 1150 | 1151 | // null, object, or array 1152 | case 'object': 1153 | 1154 | // null 1155 | if (!obj) { 1156 | return NULL; 1157 | 1158 | // array 1159 | } else if (Array.isArray(obj)) { 1160 | 1161 | let refs = new Array(obj.length); 1162 | 1163 | for (let i = 0; i < obj.length; ++i) { 1164 | refs[i] = this.serialize(obj[i]); 1165 | } 1166 | 1167 | return refs; 1168 | 1169 | // primitive typed array 1170 | } else if (ArrayBuffer.isView(obj)) { 1171 | 1172 | let ref = NULL; 1173 | 1174 | if (obj instanceof Uint8Array) { 1175 | ref = TYPED_ARRAY_OFFSET + UINT8; 1176 | } else if (obj instanceof Int8Array) { 1177 | ref = TYPED_ARRAY_OFFSET + INT8; 1178 | } else if (obj instanceof Uint16Array) { 1179 | ref = TYPED_ARRAY_OFFSET + UINT16; 1180 | } else if (obj instanceof Int16Array) { 1181 | ref = TYPED_ARRAY_OFFSET + INT16; 1182 | } else if (obj instanceof Uint32Array) { 1183 | ref = TYPED_ARRAY_OFFSET + UINT32; 1184 | } else if (obj instanceof Int32Array) { 1185 | ref = TYPED_ARRAY_OFFSET + INT32; 1186 | } else if (obj instanceof Float32Array) { 1187 | ref = TYPED_ARRAY_OFFSET + FLOAT32; 1188 | } else if (obj instanceof Float64Array) { 1189 | ref = TYPED_ARRAY_OFFSET + FLOAT64; 1190 | } 1191 | 1192 | if (ref) { 1193 | this.writer.write(UINT32, obj.buffer.byteLength); 1194 | this.writer.writeBuffer(Buffer.from(obj.buffer)); 1195 | } 1196 | 1197 | return ref; 1198 | 1199 | // object or known prototype 1200 | } else { 1201 | 1202 | // the object is a prototype 1203 | if (obj.constructor) { 1204 | 1205 | // a known tbjson prototype to be added, or a lookup if not known 1206 | let code = obj.constructor.tbjson ? this.registerPrototype(obj.constructor) : this.protoRefs[obj.constructor.name]; 1207 | 1208 | if (code != null) { 1209 | 1210 | // unbuild 1211 | if (obj.constructor.tbjson && obj.constructor.tbjson.unbuild) { 1212 | obj = obj.constructor.tbjson.unbuild(obj); 1213 | } 1214 | 1215 | // process the prototype definition 1216 | this.serializeDef(obj, this.protos[code].definition); 1217 | 1218 | return code; 1219 | } 1220 | } 1221 | 1222 | // simple object, traverse accordingly 1223 | 1224 | let ref = {}; 1225 | 1226 | for (let key in obj) { 1227 | ref[key] = this.serialize(obj[key]); 1228 | } 1229 | 1230 | return ref; 1231 | } 1232 | } 1233 | } 1234 | 1235 | /** 1236 | * Parse a definition, but only return the portion that matches the selector. 1237 | * 1238 | * TODO: IMPLEMENT NULL READER TO SKIP ENTRIES FOR PERFORMANCE 1239 | * 1240 | * @param { object | array | number } def - the definition specifying how to decode the binary data 1241 | * @param { array } selector - quit early and return the value selected by this 1242 | */ 1243 | parseAtSelection(def, selector, path = [], prototype) { 1244 | 1245 | // forward a plain object 1246 | if (typeof def == 'number' && def == OBJECT) { 1247 | return this.parseAtSelection(this.objs[this.reader.read(UINT16)], selector, path); 1248 | 1249 | // forward a known prototype 1250 | } else if (typeof def == 'number' && def >= this.offsets.prototype && def < this.offsets.array) { 1251 | let proto = this.protos[def]; 1252 | return this.parseAtSelection(proto.definition ? proto.definition : this.objs[this.reader.read(UINT16)], selector, path, proto.prototype); 1253 | 1254 | // control the object path 1255 | } else if (typeof def == 'object' && !Array.isArray(def)) { 1256 | 1257 | let selection = selector.shift(); 1258 | 1259 | for (let key in def) { 1260 | if (key == selection) { 1261 | if (!selector.length) { 1262 | return this.parse(def[key], prototype); 1263 | } else { 1264 | return this.parseAtSelection(def[key], selector, path.concat([selection])); 1265 | } 1266 | } 1267 | 1268 | this.parse(def[key]); 1269 | } 1270 | 1271 | // read to the void 1272 | } else { 1273 | this.parse(def); 1274 | } 1275 | 1276 | return null; 1277 | } 1278 | 1279 | /** 1280 | * Parse a definition. 1281 | * 1282 | * @param { object | array | number } def - the definition specifying how to decode the binary data 1283 | * @param { function } [prototype] - create this type during object instantiation 1284 | */ 1285 | parse(def, prototype) { 1286 | 1287 | // type 1288 | if (typeof def == 'number') { 1289 | 1290 | // primitive 1291 | if (def < NULLABLE_OFFSET) { 1292 | 1293 | // null 1294 | if (def == NULL) { 1295 | return null; 1296 | 1297 | // unknown object or array 1298 | } else if (def == OBJECT || def == ARRAY) { 1299 | return this.parse(this.objs[this.reader.read(UINT16)]); 1300 | 1301 | // primitive 1302 | } else { 1303 | return this.reader.read(def); 1304 | } 1305 | 1306 | // nullable primitive or higher-order type 1307 | } else if (def < TYPED_ARRAY_OFFSET) { 1308 | 1309 | // non null 1310 | if (this.reader.read(UINT8)) { 1311 | 1312 | // support older versions 1313 | if (this.version < 1) { 1314 | return this.reader.read(def - NULLABLE_OFFSET); 1315 | } else { 1316 | return this.parse(def - NULLABLE_OFFSET); 1317 | } 1318 | 1319 | // null 1320 | } else { 1321 | return null; 1322 | } 1323 | 1324 | // primitive typed array 1325 | } else if (def < TYPE_OFFSET) { 1326 | return this.reader.readTypedArray(def - TYPED_ARRAY_OFFSET, this.reader.read(UINT32)); 1327 | 1328 | // type 1329 | } else if (def < this.offsets.prototype) { 1330 | 1331 | let type = this.types[def]; 1332 | 1333 | if (type) { 1334 | 1335 | let buffer; 1336 | 1337 | if (type.size) { 1338 | buffer = this.reader.readBuffer(type.size); 1339 | } else { 1340 | let length = this.reader.read(UINT16); 1341 | buffer = this.reader.readBuffer(length); 1342 | } 1343 | 1344 | if (type.ref) { 1345 | return type.deserialize(buffer); 1346 | } else { 1347 | return buffer.toString('base64'); 1348 | } 1349 | } 1350 | 1351 | // prototype 1352 | } else if (def < this.offsets.nullablePrototype) { 1353 | 1354 | let proto = this.protos[def]; 1355 | return this.parse(proto.definition ? proto.definition : this.objs[this.reader.read(UINT16)], proto.prototype); 1356 | 1357 | // nullable typed array / type / prototype 1358 | } else if (def < this.offsets.array) { 1359 | 1360 | // null 1361 | if (!this.reader.read(UINT8)) { 1362 | return null; 1363 | } 1364 | 1365 | return this.parse(def - this.offsets.nullablePrototype); 1366 | 1367 | // variable-length fixed typed array 1368 | } else if (def < this.offsets.object) { 1369 | 1370 | let length = this.reader.read(UINT32); 1371 | let objs = new Array(length); 1372 | 1373 | for (let i = 0; i < length; ++i) { 1374 | objs[i] = this.parse(def - this.offsets.array); 1375 | } 1376 | 1377 | return objs; 1378 | 1379 | // uniform object 1380 | } else { 1381 | 1382 | let length = this.reader.read(UINT32); 1383 | let obj = {}; 1384 | 1385 | for (let i = 0; i < length; ++i) { 1386 | obj[this.parse(STRING)] = this.parse(def - this.offsets.object); 1387 | } 1388 | 1389 | return obj; 1390 | } 1391 | 1392 | // fixed-length array 1393 | } else if (Array.isArray(def)) { 1394 | 1395 | let objs = new Array(def.length); 1396 | 1397 | for (let i = 0; i < def.length; ++i) { 1398 | objs[i] = this.parse(def[i]); 1399 | } 1400 | 1401 | return objs; 1402 | 1403 | // object 1404 | } else { 1405 | 1406 | let obj = prototype ? new prototype() : {}; 1407 | 1408 | for (let key in def) { 1409 | obj[key] = this.parse(def[key]); 1410 | } 1411 | 1412 | // call the build function for post construction 1413 | if (prototype && prototype.tbjson && prototype.tbjson.build) { 1414 | prototype.tbjson.build(obj); 1415 | } 1416 | 1417 | return obj; 1418 | } 1419 | } 1420 | } 1421 | 1422 | // constant types (primitive and higher order) 1423 | Tbjson.TYPES = { 1424 | NULL, 1425 | BOOL, 1426 | INT8, 1427 | UINT8, 1428 | INT16, 1429 | UINT16, 1430 | INT32, 1431 | UINT32, 1432 | FLOAT32, 1433 | FLOAT64, 1434 | STRING, 1435 | ARRAY, 1436 | OBJECT, 1437 | NULLABLE, 1438 | TYPED_ARRAY, 1439 | UNKNOWN, 1440 | VARIABLE_DEF, 1441 | INSTANCE 1442 | }; 1443 | 1444 | // custom types (user can add more using Type as a base class) 1445 | Tbjson.Type = Type; 1446 | Tbjson.Types = { BigIntType, DateType, RegexType }; 1447 | 1448 | // functions 1449 | Tbjson.cast = cast; 1450 | Tbjson.clone = clone; 1451 | Tbjson.definition = definition; 1452 | Tbjson.flattenValidation = flattenValidation; 1453 | Tbjson.serialize = serialize; 1454 | Tbjson.validate = validate; -------------------------------------------------------------------------------- /test/strings.js: -------------------------------------------------------------------------------- 1 | const STR_1 = '0'; 2 | const STR_10 = '0000000000'; 3 | const STR_127 = '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 4 | const STR_128 = '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 5 | const STR_8190 = '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 6 | const STR_8191 = '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 7 | const STR_8192 = '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 8 | const STR_16383 = '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 9 | const STR_16384 = '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; 10 | 11 | const STR_UNICODE_1 = '考'; 12 | const STR_UNICODE_31 = '𐖱考󾕨ꋳe럽D2Z鏟횒U2ֳ󊰉O༇﷊ڋۜ򬇼ߔ#듹庀ǘ(՛鐝Dm'; 13 | const STR_UNICODE_32 = 'څ{@ǵ園rߠ󋳍𪍞[㻒tѰ؄RBކߧ񡤜(મQ﶐9,j񻞀񯹓5^Оѷ'; 14 | const STR_UNICODE_33 = 'څ{@ǵ園rߠ󋳍𪍞[㻒tѰ؄RBކߧ񡤜(મQ﶐9,j񻞀񯹓a5^Оѷ'; 15 | const STR_UNICODE_127 = ')첂Ů򫐏Pᘯ𺵷혷?ν䛧ݡጇ؉Ͻ髆񆒕󭨪ƭ𑣾򗠴񬚱㬌ߥш񗟓뼨⨣b􆳗n񎵵򽲫𰀆ؾ삭Tjǐ񬻵𔕦Ղ)反񶻨١޷掽ݱÔ">ނ(ͺaO񧗉ҙްԁ拓񔱬]􃀉~ُ񂷤͋ǂ𺚘䀶􇝋&e񊀬~듩͗g8㺚ˈ䞒3󊇶Λԭҳ񚋣ף򜉭̉׭MOtެ䠘c卧gۑɽ9쀤󻩞佀&挨􀀀ԏW󴟗ҟ񇡀ܢι؏p*兒ᣪX�'; 16 | const STR_UNICODE_128 = 'gཀྵᶈc:툲ĥۤͅ󧮽hַ냘ڞ賠W㔗/ߚ܍΅萎ޣ蛃􆿉즖ۢɦ·꟞3邤ϵޞ򁰖Ӊ𔬋̢`P좑ھŝݳf㳥񝸟߰߄憪Häֶ飯I^唡`򡇆eٻ𭖍ϫٸf红򘱆uଷ伩䶛πң"[5 𑡁룡"OӸ󾠹aʹ㲽МↃ􋐱+卵˜Ç怌ڽÿ≜�ƅ쨘ꔃd蜲o󨒰ư𜩘󾩋=㨶OKىŮ×ѓ羾։髆⇞沀*󀜍᛬쵀'; 17 | const STR_UNICODE_4095 = '󑶭B𩼱͈MߞJኪc8󏨦񗭅|⧬KBᴤߜ藉ř�٫甴.պ,Ԉ󡋭Γ䳢ש׀B拊͹􃛭#o௅シ𩥂*䬂蓮ȴ⩙更򦟫󨼡H񆅪۲Å̪µ(ʅŖ󛯠純φ󊿊m昜𯾓ȳ衊y`ɟ賳񇊋󆜤Ӄ暦=g뚠做󭗊ԓdX𓶙Ω񇀵ϓ󞀧񵗊ھ蠶6򻄬񒭵缗|沨𧓊k󎫡҂狝Xsⷉ󖠃4򨭄G󪨖.䜔͑ӫzߍ󊊶u嘶񛈢Ć륧ʾk췛ⅻ󹋼􁤣ȴ򑭶Ȟջꨔ׳͆󓭦sⴤㆧ⁼񟚔:*킅󯺌3ʛK놈ҕ݊Ɍǚ􌿉ݟۨCڮ∴ۦ򼳜ԣcڑu냳նٹ񛄲c󠕞fꁀ͈s黯ǣ[՞ꡇ孄値թ砐򊻘񭌑쏝i񳽹䴧œ󋫹󄜱ʏs뾨憎𒍋’ے髆琲!ʵ6ų℃񭠻䑆Ɣ)𢄺󃏻^Έ\jԜ㥚֜5҆饜푈`ۨæ3в🝑책f䜐񑇼󔎞y"׍&t=�򭚲�ݴ򊠠ټ۔󻀥󥍟N㴣膅𝂡ۙ1놻;EωݩϱgD*t`ػ񔟷ȩ㫂йޓ񿡼󵣀ܮ廋껐񍳶֖7ۈ"󧸳䬦ē𑪛𖾳祳-$/򔹗ⳮ%Ւ𝼭،鸡Ӳ򨪶盞컢,c󰸠?үæķ1ƣ󌘷+ǫ,ꐟ˥%᐀N񔢓򔧅񨀘򇀞򷉤+￉�ڀ*㿊=🀓ۓ؞濯ﻠIJǀѮsѕ܇վګm񊠵㻌̪𣑉꿮򏬝ۯ󲹾x򪃤𿵜v𝧘󫽛݅Ū󱣽ڬ͈m⎨틓ȪԱ򃛵٪񞺲Ih򣭳Û!쑀۫WፖďGn􊓂̐~Ϳ悊镕񩀗@1Ҵﴉᑸ㭓󏘆𣂲Ҍ崛r⪥Iͳͻ~󝨟󦻀]ѝꕺ⇥Q׃򁭫߻샫țEuijɋگʴ񄼄س全ߓĀ񢔉𕌈g񮑥񦷻a񳮾򃱃𣉯跕󭭒񉺾ú춐󴗊񕯂ǁ썒򯞮ҷ􍾡Њ􌵶㣁]꺬򭧰𒑬ʴ򍵨Žɩ𦻄]Tb{𜏲𙈢⹭бν󮣡€gӤꄁ󍴧⃝񑆤β眮舔x󯞂ꨛh󡰿oψ󧜉ſܞ鑱󫥻򈵝L@؏w%ڥηѷ𽴗糹훕򌊎ŭ䯝%凚Pëܤ⾋pҪ̡𞓨٘"´E􀧛P򭵖​ʞx􊢯쵌󊭅ڗر>͉鴷Ã핤څ𙬴41坔)妅zꞪ靸g𣅖ʑȏ慃򈈄󜑇(ᅦ񌧎埜񼐙ޠߟ𚸃뿇󌿣ل򻟖q*󊛕􈩍坾ݣ򣂏씁ُ󐡻ᖠ溜飯󁘒CŹ&䏸򮊱\񢞾p̮^꿀嗘񋁡Y͆Մ򙏿򊉌搁o񟕖$l𧮜ȍy󭢿ے𜩠󗜾񀎶ⷑ置햕񤀆񅫓2ԉ𷧱䚢奼Nޤʝ󕳨ве𹐴𦋥Y𮄊󃀴⥍񕰷ƒ򈶷v𑎄рr쯯ᵄ񌻔ꤑ牰򪨻䤤/µ𶓹۽%f󦖟𬶁⩟򡮕ޠț|Gؖ󘶫󋝫4֪$>򵫾ó􁈅Ÿ؝…X󱜄鎫1ɼݩ򸫞񜻏қ㽸ׁ除붋ꟙ耊呅DŽ󎤪★þӚ𞾀ٞ񑜯˯烺Tć]񿾛B䈬�kYօ۠Ț󃕿R񐰬eԒ⾩ݬǚ(NgZ򺓂l򥒣Ɲ񫌱ѫW㸽"n񷙕ޯꌪ]筏偦򠝕�ů=壪H񑋇돲򭬶ၣ,󆣈멮󓌀睺t阆񋌈qbDž􄬽ɥϺ񒖒Х엖񰧰Ni䴤򆤱󏭜杩𿥒WȤԘoӳ{籌ÿາ񫻞ؗ`Қ昱䅬(ڑϪʕlꌈ󟇘餩𼦭i8ᬽ虘־_*捬옄jɞ`呋㩗D򟎞$·򶷕3𢅀jU_x𭿡ď[ྲ𞥈򵼠"ǀ׏򂡇bܫIy菐ō x˗뉨񻫸`Ϸ暦䈎n€Ο䳤Ѵ3Ќ7е򎜃򲿶񗾞Ыˊ򒵒K𭺙崄┫G邐ۋѫBs&ސ䪗4ڷщ;󌒂꽓Ҏٶp6i󵵞볰nõٲˋ򠄕0򧣢待ò򎸍𒪱ꛑ𦲳+.濬𗏤򧑹톕岉Ζ򘧜˚v򙷚ү𛐬IL⾧ĉ񼇈󪮠<랧Οoៅ_$شid1طԥՄ񔈽񪯿陿ى𐟵O񜣡p򻞿`«Wϱ︔੺ʺ񫓎ֶi򬹃[Ծ񵤧왛񑈧_ݷ⾴đŮ뼪څ耆詑߁񜚪ҽL)𚰊􁨼͹qL┬񺜡α/ǧjm㞶񨴒콷ݸе쬒A񜂋槊펚ύĺ趉ﮜȺڳ񷪤򻀞Ͱ辜PʀȈvVʞ񏼡˺Q𧫣󒳌ߍҜZ!񕦽܈󜴻󡺽Եd믱s婽뚨^ƽ쩱䵊Y񔳕滓Љ譜΁䞽Ƒᴛѱ⽿񔽆˻򃫉ɟߒ׭𶵊һ𐨋K⧘󏲳󂉟ʖ}櫧èۢ㙽𗥠𱘏찮򜋍ęإ򎹅Aָ Թ뒋X𴳷Õ􃵚򷽜벌ů-.o܅򅪬ܥ†Wڈ謆킝𸫢Ňڋ瘀&{󧝯ҭ񣵘Ɣ򝚢2ι񆪣泝ϯd󆘙򅝉ՄҾك#ꌜہ󬇈Ȼeߝ既􅦯򤹺œ򵢵퍘񏞽찫XꝘ󐸨펟ˆξ⟴񲝤N􀛃뢲暦񏹟(֪𨉿ݤ}𥵵𾔁񎂋񚢧̢򿶬T򼟊ь,񓪅񈬁𹞝ŏ򟹤Tr֏̛ 霜*򞞛գ󈁃ꔫK-^HﲶKµ󲛘옅㽁𪡘ฮ񤙾М񌡕ӫ`󅔽󣴁崐󸇣瘮g󸧙縋ﺢ򒎟暦@韔ˮ?R͓08ᙾṽ[ұ{咥엄􆭂!nЙ𣅩ΩȼEl�𽵏ڳ틷󮣮Ò卆鮯񼤇򑐿q=耐򡉞ǥe^ӥ쟛ڍ�fJ{ᆘ푶򡞱ڌ沕źӄwԉ䃨˓溩Hj䢒쬓󸎾󪪀程𼊶Ӏ횩ń󛴿暢ᅱM噯Qܳ񭒣�̵򜾸񤠯,㷚暦m󡀽򙁗tźȟ݌󶤱񺸪)ᲇC1埵眨܏ଵ󽛹Ҙ阿쒎롷ⷢ侜𶕬mS󠽴婿i񼂕񁊾𳉹¤񠼥졢 䓽ʜNϸi71Ў򗷱뛎ϰ𐇹ᘲ𹩢ذ򃒰˪󚇘áɯ@ɋ꫊򸯫Kù撞򹦧ᒏ鑙Ɖ5񤧃󓼈yB񎛛暦䉔受̭񏠦�긦ݚ螛թ̩󮠶v񣖄{컡쭐󝕝#񓉼팎Ҕ󡰪Ž឵உƕ񆶞s򈣨Ԡը񭢄Լ妼󕻺àߎچw򵳱G𲐀}䪖޹"пϕ󋼙󂺨ލъ򊫄鑘싯ܪ򾕁󹄖툈㷅虐㕿�8񊉵𧹰򛄐a!󼌤俆煚̙xԪg梾Kⱻ㱇ǎ橓ꢬ݋%z􂭼ۦ閡󦸡^౨R׃𬛛-񣠎𑄔༠[ΗʊĜī󛍜񅪊咆]vj倰1򼟟͓򕸴٘󖍩򸩺򚄿μ_7怒񈜋k򪧱ɨ񢂽񳐋ś؝콛n̩ⴼ�𣘸.Ɖ֯׈Y쭏ɔ񳌒(gdž㽢Ǎޅ螡ɁI󣟸򄵈링͡�%幭㒔𬉞rv𴕸װꁭY򵱮繚򓽪"4탢㓟Ѡ4ٽ߶W_i쫂Nό󡫐*􅈐4ⴐ@󽀽ጐ򦟑򬛟攒P׾磙򉢵娐񈩭񦬶೗˝?n𴥡񂐿=󳌌eᇝƒ۫􋇢?5Éذj︼Ԩ橲ϕεۓ򏳴ꊙQ͵W➎촐x[Lφ虌Ss䇃Ꮮh�򹵐q`샡E𧆡򺤄i3`̋㄀ᒰϿÛ7𱨽잰츴򯶳HT𽳽끙�笂Ǯ*𛙭꣯􂡧ꈼD暦އ򯳘Uڭõݭ򀓈Đ.R໵И쎿M萙񴄊񄠗{ɑ㜥ᶝPlEɒΘ󘎩󋁲E^鴒躘µ@񁫊󏍍׫Տ{픺?󐝛{ꈑ൜󣏉덯�򠵁򃅤b󕰈צ󼉜󈍪ޗ䨋N՜ቲࢄ̮À񄸵/ۧ饦ᛮLߕr󊠾򦿫󏣂ᓈ懬ߋ񊌶韡&񖜳،񚑞侌򏶯$񾄳슺Mꂝ咛蹉򲡵򇑏ଔ9󒖪Qꍁ񫛱󝝖un,e鄢୙٤񜽎𚨚񧡕�򶁥ʅ-濐ҋ!©􉖏|뤛򅼗𶐠ʄŠЌ󭚃㷞_j󉤝񠤔🞫Tq3W܉򕬦񔇵Tϙ񘇴΢rцۧˤ񁼊7󥥲貳cp󉪡讍ֲs鵻k񵻨ѝ ϗ󗅓򷄃_L�蜱r񹂐ΫŴ𳩋ޅ۽X𜉞򒊦Ҋˈ㒉쬊𳠃b㖱킇񝋵󐙷l򫘲`Bᦍؐ+𕹜ň4ů؛͆ŝ憢͸󔾶偺ᷱ󃂡꺷wꙌW񮹻Gӫ*󝚸㒃y󰍏Lh𰓚55納񿯅侕TϹ-䈝􊀲め󏿆餴鄺檛񝡞�~?杁V萠񛚺典﾿合̶񿃥򡪠𛟩 旨˔󏊀󡪌򅗼쀆$ȜI崘򴭞j񎠰󃵫ڎ7CدLJ󝘒ᖕ)榲󐃊ݱ~𝲖ǟ楋ӟ燵\ڨ7吞]I暦󛃏wᦫҨž莩񩸠*į`ӏ9򔖮䫤򂧪򼅰̜GT󏸅󉷂퀐R⳱ˑ⳶%ސͫܢ䋒QOӐAgҳ󩽑悞錙򥓌򴐹𬓑yƸꑨ&_ì񛂀驳򰈻栱𵠕٠P󹲳лݱDI왡㺍򻴑<􌲒뮗򖝬˓셷񸷳2񻎣?ӽ!ᯥӾԚEճ񆚁yǹ󜑿鞕􉻏񌤹勡㝛)TCՐЛ럊Ԣ翬v摭􀖍]툖ÿ軘򝋦f涒~ᾪeљ_ڋ뵚࢛ؤꀝ-R􈪑󹠩홫m򼇊𰵙믮˒Հޏ׋H𯏻举b펚煮㪚㔬湛򀌱񀜛 󬃟蒒󸃅윮oØ鋾ߗˇѼ򄀫мz쉒;񆝏q˰�􊔢d񯐭ܟ򗲰*񧶪񹕠䱣usшTZ󘽓Ҥݭ9Қ򑜹󴳟F𣷍񰑄򎩓𚐰߼,ꩊ왊𾟥!𚯘DŽ纲]𷴥񎢇򿷟𴁭生o7񜺦A󣿉莸莪.bV񾾯ٸ󕔬�ʶުɎλ򷏶$⠦z,拫ǛʻX𮻷򹩒־𦽋𻧤󜊶kP󁌹ؚ򁴣⭇Қ҄󎾰󀈍ˡ􊲆앭ڔ탩ӷٶ߯t’ʞ﫠욜񢡳ێʏ0ߓ𙶘)ݬ̈́/1�PػוE􇡗2ɰ񜈿&jκ𘡮Sͷ(̪ӥ⠫冷𵉊㷔bބၬ􀀁˧󲍒ç٧%~[ӆӦ񮔃󤭩P濈񅫬⏿č[Ɗ띠󯚘(𯜘֬ŋ⾴7򻪾􄳀ڒ󈑮-`㲃K՜󟱓/⾳񔡅㫙􏟱̌,򢪘䗋閷󨊬=蚅q:󳖿ԏ⒊􃖛匶܂̺Ý谂Μ+㳂򦖝򩝋㮺򗅿겈P^㰜ţijǬ�𳃮񌌱5򿏈غ̧W驽v񿄔嗚󄅟󬞽򥕑֪򬿦򽿥{񫼷ތ񰁴𕚔f񎭑ڿ먼񶆼컡f7ۤI曦ٺЯ΋,f㢴1୎⤎Ľ򲗙輆*ߋល旋钁啕񍰘򈡂⒡is򛗿rt𯗺Fĺᜒ할򅸞⭰ɭ򑸄Ɗ𱕩󎜩ӢlǃiḳR뮻1񂜵髬fֿ𱍀캏ڇ𩆑񪏘ꊘsទ𱃓۟跏⢕ȘŠ𳯊wߕ򢋳őޒⳙﹱ丅)③򚧆򻔰󇬼韥ʪv̒͝�)򥑿k𜼹@몙򺜥ᦂϚI̒С[㚂߄{􏲊輽ljĞ󑚇⢠@j𯷃컡홑#󜡔󅉕劀򆗭¶󥣠JÆ𡮞औѶڭ㸻~ᗫ񊻁Ɣ󌋢:G𗭩*v`~ड܎񬑢ᙵ񘿓񘻆ǂۖ񈀒񀗗󍻲蒏촿Y􃡾Ɉ𩥔oÒA}ڷ鬥򕖁L<șR򀺔ې[׏࠲~弪e󨡿񄄷ˁ4𐱾jɛރЉ󒂨ٝY񄒲谻𡗲D⺛ʢ뼜fϼrĈ㍳󍞐̇ɘ𢸳࠱k򟞊򝒑𸧣凃<򅻺Ѣ~ɷ̨쨦եܫx됿򑥾ӎ􀂍󄱉N⚗枻؂벛򴾇囒©򇏮񓲡l说O񠡳򂦙=򎦅V珉򇹷q齅씯꿹5ަS暦u䜂ǂ╬戝𼂌󐉉ǔ盫ܰﶃ򐾯फ峮ཛྷÔ񓊧󄦾纋򤜶ތ౟ޤ񈀒􋭉зӿ曵ඩߖH檰趤熆񽍿𚻷ʬ𷑚|ዙ>NJ룔ʖ농p񢂔aơ投򢲞䶠߆򈚊򔃳𤏱m"삧ϽǑW縉Ʌ췆㜵b쇡䮎򌦼𛹐̲󎊅Ɨ󹢲)碚zؔං␛׿򱎨ﵝ`2Ǩꬻ뜵찶jfN⭦򌚉谥]�ֻв>̅࿀]漡툥gMȽ𚚖ķG쾜LѢ뿻苋瀽ZĿɊU𒌥͙Ő㭿iڤL毥ȂR䙬゜𡒎K򍔆ϡΠ򊹅񇏜NJ!𞛮ԣι衧攮𲩤𰰼󮮘Z󪮚]á򙚁ԗ螳򃀮f1l؈ϙ欗.抗ו㛮؃ɔ񡋿אݦw1ᷴ𰄊Ǣ᡼򡑢̐򽤾󀿾𫹗򊓾謖𚑺͢򒯞򖑠5Ȫїɭ념Uy痪c󛂇쮃m馄Ѽ𦁐8Ύ𣫯󳆢́󭣵܁򅪏O䯫ߠ؜󹮞ːᆒy񯜣λဥ/󕷹P𠞄_Y󉗮Z񱗵鰪؟󩘓惱!񖤘뾏񵪮憣򊽲慨񬋸Њ񺼵5ц쒮񹤥1Yꂜ*󱷕퇚몕_ذԗ򼼛󷚻𶢣ۯtS�堁𞔚δ韝z߃̜أǵLIܔꐴ𞖇{臮ﻣñIե&+ɿӸ󢽥:򴵲_񶍂ԗԂϦ!;񼃪ƙ,.󝪿姬3󆅏򍏂ﵨ𔻜F󌇁󜷹#ƷG怬GؿC򬊫򙱫b^v棪ƹ➐񺛩梶πꤑŸI𞵽ϑޕ쵊󽖭Ƥ㠯ߙB󢱓ݗ𣬛_󗼿m}ޯɦ¥nѺ鵊=谇𝙚ﭪ󚾨򵕝񒝫̘ƴ윌񗖹㷿ʇޛ׍𖙷]򒿗񟆇ն񐥻ᾠT͘㟽T(ȋ滅뎩⒝컡틷􇉻ű𗬼䩯늲󜍩]Čҿ򻷪޶췂5(񜻪c0홾򶒻񯊕ͤ立Სv芑P󌛶򤊪񎮛g𼅜é쇲꘱綬³檰j.񦜥ڭ񕝐Iߪ㐙+³±+󾳩ʥ򱠗虇•𫗓ғ񴟎WԆ暦ϯٲݾ񛞨c컡*υݯ惝܈૙脋򕇯2饘.:􄅀琐񷮖6c脻󄯖熃𜰆ŏ򀇲0Ġ꥛𲞜ʧ餕ӹ濍ū-ϫ͕S%婡󽵇v힩򟤵踛ᱛߔ⓷񛔱ѿ󐒉ݞ_ň߉֞圕𝢿̰ʕ𥛤馌𨍄𻛍ӹՀȞ篷Dx,ƈ׮ՠ𵯏܋󠷤򀕼ɧ囦ʒ񁄔ՖЭ󟡌ǡD™ɒ׈Ž_W򲴘4󁶕􆹕LӯEŧiΐ򸀧㼌L찖箔ּ𻕚񘱶hͰ컡񑲛a&͹񊏵ꨀ샷󑑷ܯ𛮝󦟨ɀݔъ򏾇o⸆򼜊ž׃Ƕ娌㓭ὣz󀻮َ֯睩򢉷+伊r𒭷k耛ǘ䃂)fۚ񢓟oY1δۏf@𰈙+z𕎗oѓ𞥐Գ蠋[خ޾/򽑪󀋣wղǪC򁔦ܗĤ½.󳽶ȓ*𺭃ׅܻP񞮃ŏ񸮏SՄn싇Ÿ(í/(eΑG΢񄐛󾺛DюC𿩲웇﹟ǘ7z+䛾ㅺ󿵂eѷ񜔼Ӱɬ󛎦򫕷𪅀欆𭞇뛘ݠE>𠸮󋣒gՈI`동񼔨񯠣󛒧򗺗洠v�􍂟cеL󟐧򴕕ȫȠH񆳎暦ẙ󎜜ف⳼Z򤩑H𥐿糊4긞ʵ񤽉U׈Ȝ(ʃ4垇𠷿񳎐򣳿ٲ꘱򍽒򒉚􍮆𯝒ud񡔬ن&𕃴eÙ򱀘ܗˬ䊺օ㾑CሦpI컡󬕣򺜅ہҶஞĸ晷΍񖗓gڑ𿻾񇱍򯝳򴢙򌃊[鲆׊ቾ󇘙ɒ돖鮚ﶈ񁺤۞ݯǮLÂe靤򡫉煄򞶓ӡӕƽʲ寐󁾛󰏔򻶆񽆒?񬕕񷖈o𳝖ƒ樊岞-⓰6䶴̚򍔧� 򷭒𣃔,󸙰祥銽񛁡򦌚婗uݒ埓뽥ㅉL꽐ح¨͓N𭃇ᓞ򘼰􁐻ڊٖ۫č󦙍Q_򅻵ߙ𾞑�񓷒㫔􃣞񆽨蓤񀟓녗図Ք𩸛܆󱂤eƈ닛Qi򗅢򐏧3JⲒ񔈊ᘂ$s񂅹󐄺񌾍컡𠭺y%硚!G𦿦􃨭𵂦>򡳜g튂mٽ팟c𿀶󒼽C𴬔􄶕򯛖𼙢⡴򔘗္ƒfŏg[鄱𰸾ƪ⥖􂹟7𿏯󗼧斂Յ͍𐮥򉭣R񹲀͉򢕹F𪂋䥅lO暦#񼐼Ň򦅾}H񘃳ꤕ뇥:ڈ귧<𭳓𴮏򷴟󩋀蝖됳ٲ파󸃤󦖸꥗Y򣯴閜籈鬬闲􆬮񍳪㙖h-[C⭤𗧶ή§㇈󶜿߂􍑼𪌏煂沺s̿􂜎%ׄ𤪜홿l垞ָg񯎦ٛ䱜̟Խ󽤩䕖񍇨돲邳Ҧϳ򕶣P𴰬񒢣ö񞥔񬶳키2񪚌W꯯󛁢ž̿ϋ?炕暴㓹䜨כ僄Λ뵩ͧ郤Ըj,L򘌎򥥢0璖𢚜씫驽󼴈遴񌱛~􋷺֡î�ᔳ󭯤񐴼􁬚صZi𭳨󨵋򎊹ʥφȶ娢ƶ戹⁁󌌎�{㝆뒶Sຶ܋䜙񯈇㡏޽픙򲊐ц鯐홅G^һว1˨ݷਊထ񍕄쩔DۤY񆴗؟󄔛T쮰􆋏_󒚶슷(ݽ덗b񣎁󘋋ڵ񿺈۵렕򣱑�ϔ񪐉񫦩w邫ۖ֙󶸐򓭈񚵕r6ԧ7ꢄ串􉙂这򘟧𒌪鳌%񛆗Ӛ׍򍢐񱿆󰹬ôた񤰶嵪򝹷ەc"弄ʏl俅5%h𰚛,񃓣9㻜򀕇򣹦關Dֶ;쫿ě 𾆔𩣸膚٨򘗠󝗍򺏆䑵䩩䩩䩩'; 18 | const STR_UNICODE_4096 = '֒ò𳔒FGjƤO�ͦㅒ𠯟򹀬܏񘟁狀-󟯊抭Tƙ[ũ쐌滝ݣ򓨾𣞛ඈ񦞂󩻑𻒺ȱق.޹󥥸݊͢ݨښ򕄼擬򍈵Cν׷T򡂣𿋈𘣗LԢ"󫭧󤈝؈􌓏xثң򔘑򈤀l}{{"4񄛒󂺯𛠘ϋ͗[㈑頟ȘǽtfҞՕް㖊򸥓򘬙ⱂ缾񨩊򍽸򯁿Ȋڢ԰̅ҝ񹌄ꍬþ7񍤠ϼi줘ӚܵȖG_򱼵ͣʜ߂Ֆ奦ߢ񈇃󮕥᰿ݘFی󛒳.⺚-e𐿝ر`󡛣񵨨󸢚՗ס󴳱ӻv򰾹*W_੖俞𙾃󖄢<͓͠÷򗜲򱠤ґϞnҳ்3􍋾󃆤־򏚑쀙綑Jˈ󅢐ݜ񒴀︧Aޛʴ}Ȳ𥯘豂􇝸ﷰ򾴼!ż羆􃒉負 愌�򦯧𼱄r쓦蘒թ􄃝I컡Jꛗ挳C֓󏖑聆񈂆i픅߼1ǐ󍻋!𦷷񭔒򓑆᜜ك놽lƏC7ռ$񬵓�j%ك𳯱䣼ˏڤ컡~êա旔d|𮜱񂊣鋠߼ợyߧ٨ސ񷪦̞[񶌴󳯃𝬀ⳮw𐁮5J򇲑IۺTS𜝒-󔬴䁬뽑S𼍗ƾ)݃L㖵�㬰ȏ컡c떈Ӭ󿦂񊄱J󂈚m䙙zϷ򉟑ﲅ򟜒"࢓㗝񿷫ΧᒱY遑v"øͨ蝯l儂򃺾Ɇ?׏W򝵟CDž񥨘l?Ʃ񇗩zܥꋢϜ񒪀쯾򡲩𮸜뤽胏sݩm򆭳󫝈䠰򎂻R~l%g<ƚ􅄓:<쟎򰈡䩌𕏱糞Ϩ𕖴)ދ翥򶍰ވኙꂢ󙒾ؽܭ񃼺򅅞%ɱ⩟伥<@g㇠[澏આ⏉𯩹삶ʕ򷣂3VT-鹫苩:𥀱vޫ󢁾͈q҃欩p韣􄖏񒵛֌Ι̞󵉔򗃧뵽긴↞샂ꔄ򄢲~񠁸hڝ􇽤ڇ�Ŋ�릈ąƀG𮱹쉚㟛򪨷숛󋬐ɏݴOԶ񣰏⧯󭈋⯍۝֖u󫩵翎󾰒ɀ𢲟󻩆詽𙭄Rȗ癣‹󶳨ݞ󇡟묍Ȫ⓿㋀ֲ�+矴␤Ἔ໋T𚭎쨴󀡹侃:񹞓񝃜/"š䁓涕I𦄡霷DӘa5v�󗹹孋󯧷Ԣ𦿩˹ၾࠕ햤⵶眷ϡ!Ϝρܓ�⑭򞝠诟Ύꋵ񱇃ȵÄ칯("𼳶ԕ𚋋χ祿Ǜ컡򴯡鞲炉ůNjт,𖐻폥󫡛J􍭌D⦥&ߢ䁁§񃁴㄀׹F򺉼񐊙򞓞ԈІ񊤺䴩C𵲮򷒾u񅦹٠"𓮦E઄߀v𾔪ȝ܅뿹񥳇穃깐󚷗񙭺񮎃ә镗{𒌯ƒᎀ¯2Ӳ񿺋򖥇ܥڭ򭝓﷗댪񅊱ë򴭽ˠ򢮭͸ࣦȶ遒ō𞭧蘍r񪃍阹ճ"雨᯺य5!鲚SA<<𛀷ϨŐ/^Ռ�׉䌍ٗM𭇭a2Ѝ챠ȻnǠ񅳎ݩ“񡯯򩕩걇񚮬鍩Ϟ񷔈ؑU}󡒌ө󴲏򳋮D쉖,爆鱊𡑷X-ꟷ`^㌨!֏Ůbd񜨲4-⍘玹ס򿨭C姃ޝ񢑓ݹ`󱗦˶񭾩ᰩ󽽯ݸ󲴵ƽ򮬴VҌ򍮞摉뛺≦𧝻柍V쑾분ӨȨ¶𼁕j슢򖪾ʿS輪œJ񢺋}Ợ擣꿤ӛjگ񊗻𽇵-ꔱչf󔌡򅐀ὦ񱍈ζ팶Ǡ񫶠񘃣뗠ᨆޯӐ蘄]򬠱컡!M-LۄĄ⾀固ޏ𾣐퐃∯ɞӲ񋟌ևNݠU﵏޼󨻂na󸖌㏈󷆁唇ۧ箠^ȍ݆򩞻򥴹l噼P&nœު𙎚2؁{禰񋣭󖊸ﻜ=9݄׵󙩅󁈋͔̄浱簆呭Ӆ7ĕY󃯑f꛼񫻚ﭔ􇑮񓜂̚Ih۸$,:S܃8N令싰旛Z0b𮍀󁼑ǀ񥐁󚳒8v֑򤞮𓽥yⳃ컡쁅椧𣝬/𞿓竞󄊮對dž戃閿񉡿ཻ˧􇣾IJĸ𗝍񎽠큙p齿㫊⁦㶆󇬹e큶uљ򪚟Q㘹ৠ🭧򨬟/#[>`+ꀭܽ졕㏠񵘦𙚪􆾥𚗵𸈞ˬB޾tᙺ܎�ci뫀𥖞!Ѷٿ򔪎ˆ򧸮Ь"͊󀭠񪓅路὿𲤲c"}ᄱ񿿇񞇀󦸊M񂋱{5𝌳Z򧡄肻ꍑ뾩𧯙Mݳ!῀"󙪔򇃖䅫o֎䞡ڤ<ٿ𽕖ῲ󃏓򶼢Lĝ<ĞÞ߹⒆Ù恉ϾЍ᱄򰈫󙴩‒ϣҧ񟼬񎁩ˍ曎ꡉ븾윃㉎򸆵 쁻ի�㜟񒃹򓆗뚏G𫶲𔶤Ӂ|5`?󰙹๻Sޡ𐷏 宼젛豁򞏃񈑈yYԳA沦񓷀둇򒴴󥃳О玛󨌞ڋ垍y6񯦂򝡱󶫅뮁Ż܆΄񓆲驶𴶅ޚd洭ʤ/졏w𲎣􏣮ǯ;􈵊͎=q됵𳉄򆵃󏼓̊򆤐/зҵ魵r兀XﻦӀæ𦬟y컔A˖񸥾탟O颦񑇟񚩂񰁧򌽺A𰡽⎍>띥ⶺh6搉uڡ4𱜋ܙ "-𿸥󏜅܄כ٦Ű䚆򷕧󛶙򁺻9ii믃<袽K嚾񄌻}恤󐷵񛊗K􄜥񫼘7㎭󒀃᠟>d컡񅟚*򘉼굋,󳭊#眽Ħ򱿊󛙣=ԆOهѹrE󲱥榥䥴ޢ񍭘〵p񑈫򵔘ȉք㳖󐻚󠠀�ĺ󘭼챴򸛛񒥪J膖Ϛq楲A򃜪p>l土{𘫶񅆙򯣻詜ؽ𕻂ډ𮼃󝍐ᗈLຮӑ򮆦;۶󥥙rŲҮﹺϱ򝎿ɚ򩃷䁈򘴆٘±2󠻝󼄿dZꮮ񊦊釹񀎒𪵫TŠ,𿦐󘟋G氰ŵ^﫳䱬̭ʳ󞷫%󚄟Ȍ軃䗾<Ԯ辵4砌ļ𯟘|컡湾↩螑Ψέ^䦰H󄡃�枿Ôơجʢ؎F굒�c0v򆞰栦묾eՆ˜˄輰Ƣķ慵Pl񵎞ﳑh򡡦ӧ󴮿󑙣꺽񪫰?گ禓𩴺؅ȥAܔ簊o񨥻ι͓;𮚿ԣ􏲙񌎔їy€Œઇࣝ񦢿Ł枙Ϗʶ줨ݣ7㵤wՄn򼞨򜱬ѢѰ󃘨񹨱󹙼h𵆯)ӊ򰢞򭀷藮_鋗㩣輻΃媾􉐺_yÂ,򵽚Mvó⭰�j񕁍+B􅺵턡տ򆻈z⺒ˑ9B򪧮L̅򵬏􁒖𙣗˴jό៧Ї鯫󰒋謇qT:ḟΖፀьÒ򊲶@򥸩^ᖗ񎋠컡mϖ@󼮸Nܐ˩~򟐱cʞz—dﭷ񊫀Ů󤟖ʲ榒񪸖P󶾸e󦒯es򆱼굷ǝYZ񭵙񌑘Gd釬񻐨ⰳ(ж㊼Ƃꮗ򊷣󽼋񚕔*򩔱ܯ!ү񥹋𢪑愫Wѣյꨘ򙦍ޜ?󙞖̵鷈恔񴚨bsɴ7񙲳鋠󸴨;4YЅ񃽔梮|򨥧^VѲYɀ䲰ӱԯ󮁷ڐĶ𳃯ʍ%pzͰ𪿾݅팃ƠꐔX󕹀Hߙ[ྥ围򂎡񼑜TљJ򍆆Ů⭥ܰ{閂؟ơ񡈦񗒁,(󉇭ԇ@򛐈aﱊ񿀏􌺓𱃆񧏮;㌚ũ}o캣촫X󶥅󶬨􃊕וꄘ󭢍YpʽH胨릞𬼔H怇$沒콱򡍨턂쯶Ίħ񉎫ˠ撳ŷ𚋋z†椩ՀגʮŁC𺑡쪄ų񠱖򹶵倞љ∪B$娊=NᤍЫXⷬѰ򙐚ǚƶ׸Q߁읪􌟂⓭$⢀񩨱T%㤛𽟴ڤ찝_f䔶򭳀𥘙Ș𫭒孔7Щîۿ؋Ż 6}󿏚H񬯄@󗭝󲟖񰣃ڃ󐠔Ꞙᴷͺ嵡鮓֫妰꣠~툨ϛ򺰖஼ඳs&롕ĹE𿚲x񶬁棘�ϡ`҆m9MቋӖᜪꍱ꒾װ򢷙疊𜱣𒼳󫃘Һô#ĭ񙕓荻ƱMYغ˘㔚Aݖ󢸁M𗱃纁񃮏􈴛Ξy񾸦򬤸񼾼oˀ󕎫񞔽⩝˜2)񔭛٘ׯᖎ@f}잣抪󨗴򗣬霣뢹w٬󄥉Ϫ؍ʍ5쨎󓪰]񉊳𑡝ɣ􍩟(F񁻰[n֒ݰϸ􌡴񯬇󧉰ꮩ䊡뷫캸󵊫8󝍅ሉ.Ί򌦘ߛV󀎧&Ϋ򬬤񯭛轞~嗉ᗩ🧊鞙𽰒OŒ$y𰛕󉑁ʽ*񱻕ۊ݊ܩԠօ臒輑󵍘󎱨ɭ񔆉񹬜򱪕ᒟ󓗭ɰ􏮣󂪔#󾛌眐৷ޫրn򠈑CC󻩈-񱆎Ϙ򟈟b՝碊tס飠Ը񏇿椬ⵢŸ僽󕓬_ӲƊ퓈򩥂?뛚s?~0ꯏ&#惻ڞY?ò󼗺񟇘0柭󣉔˓ź񞚻$o񪀅ՙї񹽣%ɮب󤡋򬹪ǹ񧢸)V󸘻յ󔘫«#0�ꢒ􇀊󉼥ŅLj̅2(򒩳L󰳓嬗Xʟ+ؒ捲ŦX؊}󐃛𮰉뮁._žƙʂ¦潢륈װ񳕧[㆝s)׷.ڑꪇð񅑝뗋,Ѣο۬쏪WVᳫ͉ɈᡱڃVH󧪰q祌㕎>Ν𘭜鲂󛚉T6󴗜lϟՍۻƹ̊n@u􊋪(4䥳ѵ:҈頋ͼӂɋޮ�oƢ񣘬fꈩƧy磘𞩖f0嘇􃜜񖕧񑸭՟ǘ뉳הKjᛓy✴𘰎煪쓒󇄱鐝꣞暱焨󅸑ͬ㽮aj𿥤򅁕㽇!‡񐏃꽵ַ󒇨m5׳㛔˗䶓󫂞𻲂컡П𢈆ݶ񰗭轰󰎂񆁐}񜟖Ҽ܍+𽹢׈D󴅤Q𴯍녓񜏀,}֫򡊕8Sq󥳂誓颞캍鼼٭񃡩L񑚚¿Ũư쥅㕲𑚋5B@᛻ۘؾ՟�񨽧"򁐺󋞰󥓽򫿜ꙇ̸】󷺳񪽖Ѧ>+ҹ≣󻲒6񅹓􈑝椓𭮀򐉚󯈷(Фy`񘎄顢𥵀ǎ󪨈/ώⲁꏆ醽򦅥힢谫ϒ㯤�託9췦␿ܯ᳚񴇮ȡ5Ck󡁑暲󴰬􎛷xn狵񍍍ˈ괎ŘW(蠱੼̀ԛ컡ҋЫjᖐ<ꢷ𬀮ꏤ蜿t ګ$ńC񸲒䂈3㊷ݤ@շ򨜴◟[󪗮O֯֩Ù򼠏􎼔⌻3ʊt􍘸7>⼗MŘݰ󵾶򅗫񵴾.Z𰛵󝲶󡝁陵􂂳򘮀_ՌҞ򘉩𖽰󳀊@ѻ܇Dd޳޻ɂ󼪺񉞲�̮í𔎷媋瀄񫤳ι󓵝G򅏁򡅬;ࢊ񱢰񯻫﷍ѱ+a񨽟򂑆󅬽ӧűʶ[p㦮𙞲򟳞]Y⥰玀(مޑɑ󺙑񶗩Y򈥫"_ŭў􁈁ᅹ񢅼ɿ暢١◂׉直vЬ]且򎣿󱺚񗶆󙋑Ɠ9ɠ(􋽀Oڞ純񉁞룹寥􈃆Ѻ󊦪𑤋ۢढ़򼦎臖ՊƔ𒙉ዟ񌥏޻y󡯎e5銿ں󢺜󛽂𾚐𶯍垯}뷖$শ𴑜ɘろ㇧񚶛cP󿃴󅬇񬧲ݶi䳰롔챠ۡL󦊺򶏳ҵGﯚ̜꬧󑚧󳼜蚫VVὙ|򗌈𯼂)ܻެ敐˿ʌɚ󐳜񵮨У󳌵􉆾򩧌ⳋ𗃯Of퇕󩙍iZ吋Ȣ|1֕x󢜡𥐈􍍁R񎽹􃋚zRW7ð⫔еڌpát뒠ڪ񋋡ǩ译㎥ǣེ𼼲鮦洤֬Pچ𛌢c׬Ӳ䁨󞛐ʈ]𾲎ǵ∓ᢎ癗젂𱩳򇟲Ͽ걣⹦C򽧝ʭ㙤ǽĹkⰷ;򅜲7󗋏YF 떞叼5ᐍ盠龼ԁuiu붵rY橙8XjG󿹞􇳾^zv6۬¤񧩪Eܙ 츢򳿂󷗤䒕Ĉ𤹇Aܧ򘂅𴃩I񡥗񖑩򼦑𕻦xa𦯈p_ૡӛ4ꠈ󝳈]݉6ٯ퐨򕈹򭒯죙60򪚔OL茢򴞑컡񜤑kѯ뭕㷋␤ݾm-ѭ󙱽R􉍔Ć࿶ȩڂ蛕9�GAPz񌔏􉒢&򴾴״̘걅,w񊜒񍇾񜞸ՄjӾ-킝񉗐¬GB퐜𧐑Ӟ飂쥃ꁾkЖޢՕ٦󯜐槔򒋅0ƶ拃ٛT毑񘵅렧剛𿫜Zij辩ɥ񂪆0邭ɜ󦍧簹ሲş𜶭蕗톣ÔÆԨƎ1컡P񓋉.􍃞ӌ𲆱奆򺁔ऩ<󪨗󕠟^󢼪Ƹ𩱷⫈cͷڔ_J΋𭵨􅫝{ޱ𶟏󤉇򇎑ۄɖ򻂎쏔킩ಫ𫂣Ή풖C-Ȥꁼd򹴧񽈪闿uܤ磁၆񪋙ط엡;旼྘߮簑񇹽𑭡j)񭗼󐆇ۜӯ|°ӳ͂򮰇瑒֒Ρؖ☜I𰁲컡ﱮʈ㧿I󅕃IR煣ꀠf򞲁ࢴɉ3򝑉湠W擑򡲟ĕɹ񹽂ߓ*вcqʼ򏝢е落84=:Ӟ򯯨󊬻tƁ񫥎ׂ95h9~外񱽃ᕏ컡ջ㻘跷Н󉥑*ſ˼Ѻ!PzOQ군󨼗ߥ񓷕8泷󐼎Ⳏ񬸥遴鹅䈨$妍کꞲ>򔍜䩥𕭾Y贌🆼4μ泌􀱤ӿʊ讻񫺍6著Ѽ࿲鼖(ܟ끡E諣"髋񘤺ᢳ*6槗髆𷖡O狸ू~֡⼗Ý𪔢݋ۤȕ2ؒ𚮥âͳPᛦ1]:򫃅7򋶾ND뻿y󏑰nἈ󎔴]򒑼ӗ柤촸컡gY澃s󘃑ǎ谝C鉨񊗰n󔄔⠾𐝝ɐ΍-筹򤽑󜰨ꖞ㪤񁌯≴𰺊ꗄƒ؅owۈä!ю}ꀶ򌪭˄2_ijڃ󧈣嘇󸍍M󂋳񪖛񭂶N񵛬ピЕ٪ȥ߽Ȓݺl䡦߇컡򩅣𗻘⽉),쾊巽󒦜򼼙+淗𺖣񻖤򪏓rãМ=д񦓲̳Лᴖ?BЖ@Ń)񽁧뿦Nͷ񆣩蕲Ŵ׶㨩䷢⺮Ľӷ󰥝󌺜򬳠񃬱񺱷뾯􂜭Ⲛڪו^gX󰴬򫑴ɳ,Ł󊯂ⷎّTZ䯷󠶨𼋾򗛕9􌽕񠺐񓿌ˮnR󳊕𡂿ﯽћΒ#v{ݮbۣ̊ﯟ-&ׯ🨹񅐲Z󵺖𱙋o@ҠǬÑB컡椡ޑP𭡭Ӂ6̯ڜ*䟆A򠘏μ­񤼘ꨧꮄtq-[󼏮򌖭X񷷓컡𳛴򶀮ߡʩ7󜩾F裭򯖊򼀯󈸒܆͏ՓVʫգ۪߈̓ߝ̱񌹊򍞢򹋳ꬑι򯥎抴䇢󷏠刌􋛿@n˶򈞗𪖓Ѥh7攋觟=͐¾鼰ױꍂ򜗒î𷚯𽏹֣ե1kúʘ妈񽄆|泿鍱kԫZxꞈج̦󺶺𒹿β𓥭ᣜͻӷ㒦KV֡󡴭󞥩…̘쓠ƠI񒆇Ž;Ӫ򲯊*L⺝뻚+񁊜胹𔊃佘_aj򇹙󣻫0ؼ괽Ϩ0覗蟐󚸛񀶶p򢳝눮显ϧ񩛯Օ쭆򉗨శ�Ġ@!㏇ꬰ󷺔浕?袶=゗ì=ă⦛󨦍󥉚񊿗񝌇〨񤰏㺷4a򂞦ﳷ"󘥹ᇄ򷶻򄟠򄴼F(򁶴򴺧扱񧌻󕰩󉸩𻊀_ر(ΓՌ떉,򻪠㪵𲭷򋆹Y0ؕX񭲴Ԥְ򂶮Y򊿕W򣹽+⼁컡࣢dxඟg𴯴󚄯뀓l䥘Ӝ㲎ށ񺆹짝񑰧ĒkۈS笝᷒*R󈝁葃𓫎Zg􀔃I𷼮㞽𷄯F/򝪟׉񡨛Հ宯󲌘L曜󃤙𢎪I񭾫ʋ[3v˽Z񯤀伈ˌټ.t֝ʥ9ߞ廖AQʢ򬓔􁥐򡧌B񡰏䟮ΐ۳댺Zᯝ簷:Hעܫ$N[ȸv񒐉Ԅ俰ٛ𽅕A򔐚𐖛洢鍶瀤C�?fǔ򆔮ᢷä🕫􋹎I5AGy򗩶홉ᗲ贴 궆⎔J批Қ򝊎وΪ̾˾梋ߊε󄟷Ө𽽂窊Gߌ񾑨ާQ󸗛𒼘;m�č汓9󀺮ްλ⪍ᦫ񬝉ݫĊ򖑚V򿕷LІŴ葎ӛˋ󀥃6rێĐ𼕼𦷐gjᤧ댄m񧱏󃩺󞞺񚭦󹰃cJ򳃖ԏỗو󉬓H󢯕嫌.򘻅龚􏊄㌈G7İoŵѾ𥘎鱈g7bW񼶏⧪ͪ㉢ԓ̤5͡𰛫􌒖a󚂾󉞎H񑰨濏Q󔚧^𗜰C'; 19 | const STR_UNICODE_4097 = '֒ò𳔒FGjƤO�ͦㅒ𠯟򹀬܏񘟁狀-󟯊抭Tƙ[ũ쐌滝ݣ򓨾𣞛ඈ񦞂󩻑𻒺ȱق.޹󥥸݊͢ݨښ򕄼擬򍈵Cν׷T򡂣𿋈𘣗LԢ"󫭧󤈝؈􌓏xثң򔘑򈤀l}{{"4񄛒󂺯𛠘ϋ͗[㈑頟ȘǽtfҞՕް㖊򸥓򘬙ⱂ缾񨩊򍽸򯁿Ȋڢ԰̅ҝ񹌄ꍬþ7񍤠ϼi줘ӚܵȖG_򱼵ͣʜ߂Ֆ奦ߢ񈇃󮕥᰿ݘFی󛒳.⺚-e𐿝ر`󡛣񵨨󸢚՗ס󴳱ӻv򰾹*W_੖俞𙾃󖄢<͓͠÷򗜲򱠤ґϞnҳ்3􍋾󃆤־򏚑쀙綑Jˈ󅢐ݜ񒴀︧Aޛʴ}Ȳ𥯘豂􇝸ﷰ򾴼!ż羆􃒉負 愌�򦯧𼱄r쓦蘒թ􄃝I컡Jꛗ挳C֓󏖑聆񈂆i픅߼1ǐ󍻋!𦷷񭔒򓑆᜜ك놽lƏC7ռ$񬵓�j%ك𳯱䣼ˏڤ컡~êա旔d|𮜱񂊣鋠߼ợyߧ٨ސ񷪦̞[񶌴󳯃𝬀ⳮw𐁮5J򇲑IۺTS𜝒-󔬴䁬뽑S𼍗ƾ)݃L㖵�㬰ȏ컡c떈Ӭ󿦂񊄱J󂈚m䙙zϷ򉟑ﲅ򟜒"࢓㗝񿷫ΧᒱY遑v"øͨ蝯l儂򃺾Ɇ?׏W򝵟CDž񥨘l?Ʃ񇗩zܥꋢϜ񒪀쯾򡲩𮸜뤽胏sݩm򆭳󫝈䠰򎂻R~l%g<ƚ􅄓:<쟎򰈡䩌𕏱糞Ϩ𕖴)ދ翥򶍰ވኙꂢ󙒾ؽܭ񃼺򅅞%ɱ⩟伥<@g㇠[澏આ⏉𯩹삶ʕ򷣂3VT-鹫苩:𥀱vޫ󢁾͈q҃欩p韣􄖏񒵛֌Ι̞󵉔򗃧뵽긴↞샂ꔄ򄢲~񠁸hڝ􇽤ڇ�Ŋ�릈ąƀG𮱹쉚㟛򪨷숛󋬐ɏݴOԶ񣰏⧯󭈋⯍۝֖u󫩵翎󾰒ɀ𢲟󻩆詽𙭄Rȗ癣‹󶳨ݞ󇡟묍Ȫ⓿㋀ֲ�+矴␤Ἔ໋T𚭎쨴󀡹侃:񹞓񝃜/"š䁓涕I𦄡霷DӘa5v�󗹹孋󯧷Ԣ𦿩˹ၾࠕ햤⵶眷ϡ!Ϝρܓ�⑭򞝠诟Ύꋵ񱇃ȵÄ칯("𼳶ԕ𚋋χ祿Ǜ컡򴯡鞲炉ůNjт,𖐻폥󫡛J􍭌D⦥&ߢ䁁§񃁴㄀׹F򺉼񐊙򞓞ԈІ񊤺䴩C𵲮򷒾u񅦹٠"𓮦E઄߀v𾔪ȝ܅뿹񥳇穃깐󚷗񙭺񮎃ә镗{𒌯ƒᎀ¯2Ӳ񿺋򖥇ܥڭ򭝓﷗댪񅊱ë򴭽ˠ򢮭͸ࣦȶ遒ō𞭧蘍r񪃍阹ճ"雨᯺य5!鲚SA<<𛀷ϨŐ/^Ռ�׉䌍ٗM𭇭a2Ѝ챠ȻnǠ񅳎ݩ“񡯯򩕩걇񚮬鍩Ϟ񷔈ؑU}󡒌ө󴲏򳋮D쉖,爆鱊𡑷X-ꟷ`^㌨!֏Ůbd񜨲4-⍘玹ס򿨭C姃ޝ񢑓ݹ`󱗦˶񭾩ᰩ󽽯ݸ󲴵ƽ򮬴VҌ򍮞摉뛺≦𧝻柍V쑾분ӨȨ¶𼁕j슢򖪾ʿS輪œJ񢺋}Ợ擣꿤ӛjگ񊗻𽇵-ꔱչf󔌡򅐀ὦ񱍈ζ팶Ǡ񫶠񘃣뗠ᨆޯӐ蘄]򬠱컡!M-LۄĄ⾀固ޏ𾣐퐃∯ɞӲ񋟌ևNݠU﵏޼󨻂na󸖌㏈󷆁唇ۧ箠^ȍ݆򩞻򥴹l噼P&nœު𙎚2؁{禰񋣭󖊸ﻜ=9݄׵󙩅󁈋͔̄浱簆呭Ӆ7ĕY󃯑f꛼񫻚ﭔ􇑮񓜂̚Ih۸$,:S܃8N令싰旛Z0b𮍀󁼑ǀ񥐁󚳒8v֑򤞮𓽥yⳃ컡쁅椧𣝬/𞿓竞󄊮對dž戃閿񉡿ཻ˧􇣾IJĸ𗝍񎽠큙p齿㫊⁦㶆󇬹e큶uљ򪚟Q㘹ৠ🭧򨬟/#[>`+ꀭܽ졕㏠񵘦𙚪􆾥𚗵𸈞ˬB޾tᙺ܎�ci뫀𥖞!Ѷٿ򔪎ˆ򧸮Ь"͊󀭠񪓅路὿𲤲c"}ᄱ񿿇񞇀󦸊M񂋱{5𝌳Z򧡄肻ꍑ뾩𧯙Mݳ!῀"󙪔򇃖䅫o֎䞡ڤ<ٿ𽕖ῲ󃏓򶼢Lĝ<ĞÞ߹⒆Ù恉ϾЍ᱄򰈫󙴩‒ϣҧ񟼬񎁩ˍ曎ꡉ븾윃㉎򸆵 쁻ի�㜟񒃹򓆗뚏G𫶲𔶤Ӂ|5`?󰙹๻Sޡ𐷏 宼젛豁򞏃񈑈yYԳA沦񓷀둇򒴴󥃳О玛󨌞ڋ垍y6񯦂򝡱󶫅뮁Ż܆΄񓆲驶𴶅ޚd洭ʤ/졏w𲎣􏣮ǯ;􈵊͎=q됵𳉄򆵃󏼓̊򆤐/зҵ魵r兀XﻦӀæ𦬟y컔A˖񸥾탟O颦񑇟񚩂񰁧򌽺A𰡽⎍>띥ⶺh6搉uڡ4𱜋ܙ "-𿸥󏜅܄כ٦Ű䚆򷕧󛶙򁺻9ii믃<袽K嚾񄌻}恤󐷵񛊗K􄜥񫼘7㎭󒀃᠟>d컡񅟚*򘉼굋,󳭊#眽Ħ򱿊󛙣=ԆOهѹrE󲱥榥䥴ޢ񍭘〵p񑈫򵔘ȉք㳖󐻚󠠀�ĺ󘭼챴򸛛񒥪J膖Ϛq楲A򃜪p>l土{𘫶񅆙򯣻詜ؽ𕻂ډ𮼃󝍐ᗈLຮӑ򮆦;۶󥥙rŲҮﹺϱ򝎿ɚ򩃷䁈򘴆٘±2󠻝󼄿dZꮮ񊦊釹񀎒𪵫TŠ,𿦐󘟋G氰ŵ^﫳䱬̭ʳ󞷫%󚄟Ȍ軃䗾<Ԯ辵4砌ļ𯟘|컡湾↩螑Ψέ^䦰H󄡃�枿Ôơجʢ؎F굒�c0v򆞰栦묾eՆ˜˄輰Ƣķ慵Pl񵎞ﳑh򡡦ӧ󴮿󑙣꺽񪫰?گ禓𩴺؅ȥAܔ簊o񨥻ι͓;𮚿ԣ􏲙񌎔їy€Œઇࣝ񦢿Ł枙Ϗʶ줨ݣ7㵤wՄn򼞨򜱬ѢѰ󃘨񹨱󹙼h𵆯)ӊ򰢞򭀷藮_鋗㩣輻΃媾􉐺_yÂ,򵽚Mvó⭰�j񕁍+B􅺵턡տ򆻈z⺒ˑ9B򪧮L̅򵬏􁒖𙣗˴jό៧Ї鯫󰒋謇qT:ḟΖፀьÒ򊲶@򥸩^ᖗ񎋠컡mϖ@󼮸Nܐ˩~򟐱cʞz—dﭷ񊫀Ů󤟖ʲ榒񪸖P󶾸e󦒯es򆱼굷ǝYZ񭵙񌑘Gd釬񻐨ⰳ(ж㊼Ƃꮗ򊷣󽼋񚕔*򩔱ܯ!ү񥹋𢪑愫Wѣյꨘ򙦍ޜ?󙞖̵鷈恔񴚨bsɴ7񙲳鋠󸴨;4YЅ񃽔梮|򨥧^VѲYɀ䲰ӱԯ󮁷ڐĶ𳃯ʍ%pzͰ𪿾݅팃ƠꐔX󕹀Hߙ[ྥ围򂎡񼑜TљJ򍆆Ů⭥ܰ{閂؟ơ񡈦񗒁,(󉇭ԇ@򛐈aﱊ񿀏􌺓𱃆񧏮;㌚ũ}o캣촫X󶥅󶬨􃊕וꄘ󭢍YpʽH胨릞𬼔H怇$沒콱򡍨턂쯶Ίħ񉎫ˠ撳ŷ𚋋z†椩ՀגʮŁC𺑡쪄ų񠱖򹶵倞љ∪B$娊=NᤍЫXⷬѰ򙐚ǚƶ׸Q߁읪􌟂⓭$⢀񩨱T%㤛𽟴ڤ찝_f䔶򭳀𥘙Ș𫭒孔7Щîۿ؋Ż 6}󿏚H񬯄@󗭝󲟖񰣃ڃ󐠔Ꞙᴷͺ嵡鮓֫妰꣠~툨ϛ򺰖஼ඳs&롕ĹE𿚲x񶬁棘�ϡ`҆m9MቋӖᜪꍱ꒾װ򢷙疊𜱣𒼳󫃘Һô#ĭ񙕓荻ƱMYغ˘㔚Aݖ󢸁M𗱃纁񃮏􈴛Ξy񾸦򬤸񼾼oˀ󕎫񞔽⩝˜2)񔭛٘ׯᖎ@f}잣抪󨗴򗣬霣뢹w٬󄥉Ϫ؍ʍ5쨎󓪰]񉊳𑡝ɣ􍩟(F񁻰[n֒ݰϸ􌡴񯬇󧉰ꮩ䊡뷫캸󵊫8󝍅ሉ.Ί򌦘ߛV󀎧&Ϋ򬬤񯭛轞~嗉ᗩ🧊鞙𽰒OŒ$y𰛕󉑁ʽ*񱻕ۊ݊ܩԠօ臒輑󵍘󎱨ɭ񔆉񹬜򱪕ᒟ󓗭ɰ􏮣󂪔#󾛌眐৷ޫրn򠈑CC󻩈-񱆎Ϙ򟈟b՝碊tס飠Ը񏇿椬ⵢŸ僽󕓬_ӲƊ퓈򩥂?뛚s?~0ꯏ&#惻ڞY?ò󼗺񟇘0柭󣉔˓ź񞚻$o񪀅ՙї񹽣%ɮب󤡋򬹪ǹ񧢸)V󸘻յ󔘫«#0�ꢒ􇀊󉼥ŅLj̅2(򒩳L󰳓嬗Xʟ+ؒ捲ŦX؊}󐃛𮰉뮁._žƙʂ¦潢륈װ񳕧[㆝s)׷.ڑꪇð񅑝뗋,Ѣο۬쏪WVᳫ͉ɈᡱڃVH󧪰q祌㕎>Ν𘭜鲂󛚉T6󴗜lϟՍۻƹ̊n@u􊋪(4䥳ѵ:҈頋ͼӂɋޮ�oƢ񣘬fꈩƧy磘𞩖f0嘇􃜜񖕧񑸭՟ǘ뉳הKjᛓy✴𘰎煪쓒󇄱鐝꣞暱焨󅸑ͬ㽮aj𿥤򅁕㽇!‡񐏃꽵ַ󒇨m5׳㛔˗䶓󫂞𻲂컡П𢈆ݶ񰗭轰󰎂񆁐}񜟖Ҽ܍+𽹢׈D󴅤Q𴯍녓񜏀,}֫򡊕8Sq󥳂誓颞캍鼼٭񃡩L񑚚¿Ũư쥅㕲𑚋5B@᛻ۘؾ՟�񨽧"򁐺󋞰󥓽򫿜ꙇ̸】󷺳񪽖Ѧ>+ҹ≣󻲒6񅹓􈑝椓𭮀򐉚󯈷(Фy`񘎄顢𥵀ǎ󪨈/ώⲁꏆ醽򦅥힢谫ϒ㯤�託9췦␿ܯ᳚񴇮ȡ5Ck󡁑暲󴰬􎛷xn狵񍍍ˈ괎ŘW(蠱੼̀ԛ컡ҋЫjᖐ<ꢷ𬀮ꏤ蜿t ګ$ńC񸲒䂈3㊷ݤ@շ򨜴◟[󪗮O֯֩Ù򼠏􎼔⌻3ʊt􍘸7>⼗MŘݰ󵾶򅗫񵴾.Z𰛵󝲶󡝁陵􂂳򘮀_ՌҞ򘉩𖽰󳀊@ѻ܇Dd޳޻ɂ󼪺񉞲�̮í𔎷媋瀄񫤳ι󓵝G򅏁򡅬;ࢊ񱢰񯻫﷍ѱ+a񨽟򂑆󅬽ӧűʶ[p㦮𙞲򟳞]Y⥰玀(مޑɑ󺙑񶗩Y򈥫"_ŭў􁈁ᅹ񢅼ɿ暢١◂׉直vЬ]且򎣿󱺚񗶆󙋑Ɠ9ɠ(􋽀Oڞ純񉁞룹寥􈃆Ѻ󊦪𑤋ۢढ़򼦎臖ՊƔ𒙉ዟ񌥏޻y󡯎e5銿ں󢺜󛽂𾚐𶯍垯}뷖$শ𴑜ɘろ㇧񚶛cP󿃴󅬇񬧲ݶi䳰롔챠ۡL󦊺򶏳ҵGﯚ̜꬧󑚧󳼜蚫VVὙ|򗌈𯼂)ܻެ敐˿ʌɚ󐳜񵮨У󳌵􉆾򩧌ⳋ𗃯Of퇕󩙍iZ吋Ȣ|1֕x󢜡𥐈􍍁R񎽹􃋚zRW7ð⫔еڌpát뒠ڪ񋋡ǩ译㎥ǣེ𼼲鮦洤֬Pچ𛌢c׬Ӳ䁨󞛐ʈ]𾲎ǵ∓ᢎ癗젂𱩳򇟲Ͽ걣⹦C򽧝ʭ㙤ǽĹkⰷ;򅜲7󗋏YF 떞叼5ᐍ盠龼ԁuiu붵rY橙8XjG󿹞􇳾^zv6۬¤񧩪Eܙ 츢򳿂󷗤䒕Ĉ𤹇Aܧ򘂅𴃩I񡥗񖑩򼦑𕻦xa𦯈p_ૡӛ4ꠈ󝳈]݉6ٯ퐨򕈹򭒯죙60򪚔OL茢򴞑컡񜤑kѯ뭕㷋␤ݾm-ѭ󙱽R􉍔Ć࿶ȩڂ蛕9�GAPz񌔏􉒢&򴾴״̘걅,w񊜒񍇾񜞸ՄjӾ-킝񉗐¬GB퐜𧐑Ӟ飂쥃ꁾkЖޢՕ٦󯜐槔򒋅0ƶ拃ٛT毑񘵅렧剛𿫜Zij辩ɥ񂪆0邭ɜ󦍧簹ሲş𜶭蕗톣ÔÆԨƎ1컡P񓋉.􍃞ӌ𲆱奆򺁔ऩ<󪨗󕠟^󢼪Ƹ𩱷⫈cͷڔ_J΋𭵨􅫝{ޱ𶟏󤉇򇎑ۄɖ򻂎쏔킩ಫ𫂣Ή풖C-Ȥꁼd򹴧񽈪闿uܤ磁၆񪋙ط엡;旼྘߮簑񇹽𑭡j)񭗼󐆇ۜӯ|°ӳ͂򮰇瑒֒Ρؖ☜I𰁲컡ﱮʈ㧿I󅕃IR煣ꀠf򞲁ࢴɉ3򝑉湠W擑򡲟ĕɹ񹽂ߓ*вcqʼ򏝢е落84=:Ӟ򯯨󊬻tƁ񫥎ׂ95h9~外񱽃ᕏ컡ջ㻘跷Н󉥑*ſ˼Ѻ!PzOQ군󨼗ߥ񓷕8泷󐼎Ⳏ񬸥遴鹅䈨$妍کꞲ>򔍜䩥𕭾Y贌🆼4μ泌􀱤ӿʊ讻񫺍6著Ѽ࿲鼖(ܟ끡E諣"髋񘤺ᢳ*6槗髆𷖡O狸ू~֡⼗Ý𪔢݋ۤȕ2ؒ𚮥âͳPᛦ1]:򫃅7򋶾ND뻿y󏑰nἈ󎔴]򒑼ӗ柤촸컡gY澃s󘃑ǎ谝C鉨񊗰n󔄔⠾𐝝ɐ΍-筹򤽑󜰨ꖞ㪤񁌯≴𰺊ꗄƒ؅owۈä!ю}ꀶ򌪭˄2_ijڃ󧈣嘇󸍍M󂋳񪖛񭂶N񵛬ピЕ٪ȥ߽Ȓݺl䡦߇컡򩅣𗻘⽉),쾊巽󒦜򼼙+淗𺖣񻖤򪏓rãМ=д񦓲̳Лᴖ?BЖ@Ń)񽁧뿦Nͷ񆣩蕲Ŵ׶㨩䷢⺮Ľӷ󰥝󌺜򬳠񃬱񺱷뾯􂜭Ⲛڪו^gX󰴬򫑴ɳ,Ł󊯂ⷎّTZ䯷󠶨𼋾򗛕9􌽕񠺐񓿌ˮnR󳊕𡂿ﯽћΒ#v{ݮbۣ̊ﯟ-&ׯ🨹񅐲Z󵺖𱙋o@ҠǬÑB컡椡ޑP𭡭Ӂ6̯ڜ*䟆A򠘏μ­񤼘ꨧꮄtq-[󼏮򌖭X񷷓컡𳛴򶀮ߡʩ7󜩾F裭򯖊򼀯󈸒܆͏ՓVʫգ۪߈̓ߝ̱񌹊򍞢򹋳ꬑι򯥎抴䇢󷏠刌􋛿@n˶򈞗𪖓Ѥh7攋觟=͐¾鼰ױꍂ򜗒î𷚯𽏹֣ե1kúʘ妈񽄆|泿鍱kԫZxꞈج̦󺶺𒹿β𓥭ᣜͻӷ㒦KV֡󡴭󞥩…̘쓠ƠI񒆇Ž;Ӫ򲯊*L⺝뻚+񁊜胹𔊃佘_aj򇹙󣻫0ؼ괽Ϩ0覗蟐󚸛񀶶p򢳝눮显ϧ񩛯Օ쭆򉗨శ�Ġ@!㏇ꬰ󷺔浕?袶=゗ì=ă⦛󨦍󥉚񊿗񝌇〨񤰏㺷4a򂞦ﳷ"󘥹ᇄ򷶻򄟠򄴼F(򁶴򴺧扱񧌻󕰩󉸩𻊀_ر(ΓՌ떉,򻪠㪵𲭷򋆹Y0ؕX񭲴Ԥְ򂶮Y򊿕W򣹽+⼁컡࣢dxඟg𴯴󚄯뀓l䥘Ӝ㲎ށ񺆹짝񑰧ĒkۈS笝᷒*R󈝁葃𓫎Zg􀔃I𷼮㞽𷄯F/򝪟׉񡨛Հ宯󲌘L曜󃤙𢎪I񭾫ʋ[3v˽Z񯤀伈ˌټ.t֝ʥ9ߞ廖AQʢ򬓔􁥐򡧌BB񡰏䟮ΐ۳댺Zᯝ簷:Hעܫ$N[ȸv񒐉Ԅ俰ٛ𽅕A򔐚𐖛洢鍶瀤C�?fǔ򆔮ᢷä🕫􋹎I5AGy򗩶홉ᗲ贴 궆⎔J批Қ򝊎وΪ̾˾梋ߊε󄟷Ө𽽂窊Gߌ񾑨ާQ󸗛𒼘;m�č汓9󀺮ްλ⪍ᦫ񬝉ݫĊ򖑚V򿕷LІŴ葎ӛˋ󀥃6rێĐ𼕼𦷐gjᤧ댄m񧱏󃩺󞞺񚭦󹰃cJ򳃖ԏỗو󉬓H󢯕嫌.򘻅龚􏊄㌈G7İoŵѾ𥘎鱈g7bW񼶏⧪ͪ㉢ԓ̤5͡𰛫􌒖a󚂾󉞎H񑰨濏Q󔚧^𗜰C'; 20 | 21 | export default function(Tbjson, stringify) { 22 | 23 | let tbjson = new Tbjson(); 24 | 25 | class A { 26 | strNull = null; 27 | str0 = ''; 28 | str1 = STR_1; 29 | str10 = STR_10; 30 | str127 = STR_127; 31 | str128 = STR_128 32 | str8191 = STR_8191; 33 | str8192 = STR_8192; 34 | str16383 = STR_16383; 35 | str16384 = STR_16384; 36 | strUnicode1 = STR_UNICODE_1; 37 | strUnicode31 = STR_UNICODE_31; 38 | strUnicode32 = STR_UNICODE_32; 39 | strUnicode33 = STR_UNICODE_33; 40 | strUnicode127 = STR_UNICODE_127; 41 | strUnicode128 = STR_UNICODE_128; 42 | strUnicode4095 = STR_UNICODE_4095; 43 | strUnicode4096 = STR_UNICODE_4096; 44 | strUnicode4097 = STR_UNICODE_4097; 45 | } 46 | A.tbjson = { 47 | definition: { 48 | strNull: Tbjson.TYPES.STRING, 49 | str0: Tbjson.TYPES.STRING, 50 | str1: Tbjson.TYPES.STRING, 51 | str10: Tbjson.TYPES.STRING, 52 | str127: Tbjson.TYPES.STRING, 53 | str128: Tbjson.TYPES.STRING, 54 | str8191: Tbjson.TYPES.STRING, 55 | str8192: Tbjson.TYPES.STRING, 56 | str16383: Tbjson.TYPES.STRING, 57 | str16384: Tbjson.TYPES.STRING, 58 | strUnicode1: Tbjson.TYPES.STRING, 59 | strUnicode31: Tbjson.TYPES.STRING, 60 | strUnicode32: Tbjson.TYPES.STRING, 61 | strUnicode33: Tbjson.TYPES.STRING, 62 | strUnicode127: Tbjson.TYPES.STRING, 63 | strUnicode128: Tbjson.TYPES.STRING, 64 | strUnicode4095: Tbjson.TYPES.STRING, 65 | strUnicode4096: Tbjson.TYPES.STRING, 66 | strUnicode4097: Tbjson.TYPES.STRING 67 | } 68 | }; 69 | 70 | let x = { 71 | a: new A(), 72 | strNull: null, 73 | str0: '', 74 | str1: STR_1, 75 | str10: STR_10, 76 | str127: STR_127, 77 | str128: STR_128, 78 | str8190: STR_8190, 79 | str8191: STR_8191, 80 | str8192: STR_8192, 81 | str16383: STR_16383, 82 | str16384: STR_16384, 83 | strUnicode1: STR_UNICODE_1, 84 | strUnicode31: STR_UNICODE_31, 85 | strUnicode32: STR_UNICODE_32, 86 | strUnicode33: STR_UNICODE_33, 87 | strUnicode127: STR_UNICODE_127, 88 | strUnicode128: STR_UNICODE_128, 89 | strUnicode4095: STR_UNICODE_4095, 90 | strUnicode4096: STR_UNICODE_4096, 91 | strUnicode4097: STR_UNICODE_4097 92 | }; 93 | 94 | // typed can't be null, it'll become an empty string 95 | x.a.strNull = ''; 96 | 97 | return [ 98 | stringify(x), 99 | stringify(tbjson.parseBuffer(tbjson.serializeToBuffer(x))) 100 | ]; 101 | } --------------------------------------------------------------------------------