@live-change/serialization 0.9.139
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/binary.js +129 -0
- package/index.js +7 -0
- package/package.json +10 -0
- package/serialization.js +81 -0
- package/string.js +104 -0
- package/stringKey.js +180 -0
- package/test/stringKey.js +144 -0
package/binary.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const textEncoder = new TextEncoder()
|
|
2
|
+
const textDecoder = new TextDecoder()
|
|
3
|
+
|
|
4
|
+
/** Writer that will write data in binary format. */
|
|
5
|
+
class BinaryWriter {
|
|
6
|
+
#outputBuffer = new ArrayBuffer(1024)
|
|
7
|
+
#outputPosition = 0
|
|
8
|
+
#outputView = new DataView(this.#outputBuffer)
|
|
9
|
+
#structureBuffer
|
|
10
|
+
#structurePosition = 0
|
|
11
|
+
#structureView
|
|
12
|
+
constructor(structure) {
|
|
13
|
+
if(structure) {
|
|
14
|
+
this.#structureBuffer = new ArrayBuffer(1024)
|
|
15
|
+
this.#structureView = new DataView(this.#structureBuffer)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
allocateMore() {
|
|
20
|
+
const newBuffer = new ArrayBuffer(this.#outputBuffer.byteLength * 2)
|
|
21
|
+
const newView = new DataView(newBuffer)
|
|
22
|
+
new Uint8Array(newBuffer).set(new Uint8Array(this.#outputBuffer))
|
|
23
|
+
this.#outputBuffer = newBuffer
|
|
24
|
+
this.#outputView = newView
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
writeNumber(num) {
|
|
28
|
+
if(this.#outputPosition + 8 > this.#outputBuffer.byteLength) this.allocateMore()
|
|
29
|
+
this.#outputView.setFloat64(this.#outputPosition, num)
|
|
30
|
+
this.#outputPosition += 8
|
|
31
|
+
return this
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
writeType(type) {
|
|
35
|
+
this.writeInteger(type)
|
|
36
|
+
return this
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
writeSize(size) {
|
|
40
|
+
this.writeInteger(size)
|
|
41
|
+
return this
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
writeString(str) {
|
|
45
|
+
const encoded = textEncoder.encode(str, new Uint8Array(this.#outputBuffer, this.#outputPosition))
|
|
46
|
+
this.writeInteger(encoded.length)
|
|
47
|
+
// copy string to output buffer
|
|
48
|
+
new Uint8Array(this.#outputBuffer, this.#outputPosition).set(encoded)
|
|
49
|
+
this.#outputPosition += encoded.length
|
|
50
|
+
return this
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
writeKey(key) {
|
|
54
|
+
const encoded = textEncoder.encode(str, new Uint8Array(this.#outputBuffer, this.#outputPosition))
|
|
55
|
+
this.writeInteger(encoded.length)
|
|
56
|
+
if(this.#structureBuffer) {
|
|
57
|
+
new Uint8Array(this.#structureBuffer, this.#structurePosition).set(encoded)
|
|
58
|
+
this.#structurePosition += encoded.length
|
|
59
|
+
return this
|
|
60
|
+
}
|
|
61
|
+
new Uint8Array(this.#outputBuffer, this.#outputPosition).set(encoded)
|
|
62
|
+
this.#outputPosition += encoded.length
|
|
63
|
+
return this
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
writeBoolean(bool) {
|
|
67
|
+
this.#outputView.setUint8(this.#outputPosition++, bool ? 1 : 0)
|
|
68
|
+
return this
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
writeNull() {
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class BinaryReader {
|
|
77
|
+
#inputBuffer
|
|
78
|
+
#inputPosition = 0
|
|
79
|
+
#inputView = new DataView(this.#inputBuffer)
|
|
80
|
+
constructor(inputBuffer, structureBuffer) {
|
|
81
|
+
this.#inputBuffer = inputBuffer
|
|
82
|
+
this.#inputView = new DataView(inputBuffer)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
readNumber() {
|
|
86
|
+
const result = this.#inputView.getFloat64(this.#inputPosition)
|
|
87
|
+
this.#inputPosition += 8
|
|
88
|
+
return result
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
readType() {
|
|
92
|
+
return this.readInteger()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
readSize() {
|
|
96
|
+
return this.readInteger()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
readString() {
|
|
100
|
+
const size = this.readInteger()
|
|
101
|
+
const result = textDecoder.decode(new Uint8Array(this.#inputBuffer, this.#inputPosition, size))
|
|
102
|
+
this.#inputPosition += size
|
|
103
|
+
return result
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
readKey() {
|
|
107
|
+
return this.readString()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
readBoolean() {
|
|
111
|
+
return this.#inputView.getUint8(this.#inputPosition++) === 1
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
readNull() {
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
import { write, read } from './serialization.js'
|
|
119
|
+
|
|
120
|
+
export function serializeToBinary(key) {
|
|
121
|
+
const writer = new BinaryWriter()
|
|
122
|
+
write(key, writer)
|
|
123
|
+
return writer.getOutput()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function deserializeFromBinary(serialized, structure) {
|
|
127
|
+
const reader = new BinaryReader(serialized, structure)
|
|
128
|
+
return read(reader)
|
|
129
|
+
}
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@live-change/serialization",
|
|
3
|
+
"version": "0.9.139",
|
|
4
|
+
"scripts": {},
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "Michał Łaszczewski <michal@laszczewski.pl>",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"description": "",
|
|
9
|
+
"gitHead": "9202c36abc25e3baf5fe39806d89c3fab203f428"
|
|
10
|
+
}
|
package/serialization.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
|
|
2
|
+
export const typeMap = Object.freeze({
|
|
3
|
+
'string': 's',
|
|
4
|
+
'number': 'n',
|
|
5
|
+
'boolean': 'b',
|
|
6
|
+
'object': 'o',
|
|
7
|
+
'null': 'z',
|
|
8
|
+
'array': 'a'
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
export const types = Object.freeze(Object.fromEntries(
|
|
12
|
+
Object.entries(typeMap).map(([type, key]) => [key, type])
|
|
13
|
+
))
|
|
14
|
+
|
|
15
|
+
/** Serialize any data using writer
|
|
16
|
+
*
|
|
17
|
+
* @param {any} data
|
|
18
|
+
* @param {Writer} writer
|
|
19
|
+
*/
|
|
20
|
+
export function write(data, writer) {
|
|
21
|
+
switch(typeof data) {
|
|
22
|
+
case 'string': return writer.writeType(typeMap.string).writeString(data)
|
|
23
|
+
case 'number': {
|
|
24
|
+
return writer.writeType(typeMap.number).writeNumber(data)
|
|
25
|
+
}
|
|
26
|
+
case 'boolean': return writer.writeType(typeMap.boolean).writeBoolean(data)
|
|
27
|
+
case 'object':
|
|
28
|
+
if(data === null) return writer.writeType(typeMap.null)
|
|
29
|
+
if(Array.isArray(data)) {
|
|
30
|
+
writer.writeType(typeMap.array).writeSize(data.length)
|
|
31
|
+
for(let value of data) write(value, writer)
|
|
32
|
+
return writer
|
|
33
|
+
}
|
|
34
|
+
const entries = Object.entries(data)
|
|
35
|
+
writer.writeType(typeMap.object).writeSize(entries.length)
|
|
36
|
+
for(let [key, value] of entries) {
|
|
37
|
+
writer.writeKey(key)
|
|
38
|
+
write(value, writer)
|
|
39
|
+
}
|
|
40
|
+
return writer
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Unsupported type: ${typeof data}`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Deserialize data using reader
|
|
47
|
+
*
|
|
48
|
+
* @param {Reader} reader
|
|
49
|
+
* @returns {any}
|
|
50
|
+
*/
|
|
51
|
+
export function read(reader) {
|
|
52
|
+
const type = reader.readType()
|
|
53
|
+
switch(type) {
|
|
54
|
+
case typeMap.string:
|
|
55
|
+
return reader.readString()
|
|
56
|
+
case typeMap.number:
|
|
57
|
+
return reader.readNumber()
|
|
58
|
+
case typeMap.boolean:
|
|
59
|
+
return reader.readBoolean()
|
|
60
|
+
case typeMap.object: {
|
|
61
|
+
const size = reader.readSize()
|
|
62
|
+
const entries = []
|
|
63
|
+
for(let i = 0; i < size; i++) {
|
|
64
|
+
const key = reader.readKey()
|
|
65
|
+
const value = read(reader)
|
|
66
|
+
entries.push([key, value])
|
|
67
|
+
}
|
|
68
|
+
return Object.fromEntries(entries)
|
|
69
|
+
}
|
|
70
|
+
case typeMap.null:
|
|
71
|
+
return null
|
|
72
|
+
case typeMap.array: {
|
|
73
|
+
const size = reader.readSize()
|
|
74
|
+
const array = []
|
|
75
|
+
for(let i = 0; i < size; i++) array.push(read(reader))
|
|
76
|
+
return array
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
throw new Error(`Unsupported type: ${type}`)
|
|
80
|
+
}
|
|
81
|
+
}
|
package/string.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** String writer that writer data to human readable string */
|
|
2
|
+
export class StringWriter {
|
|
3
|
+
#outputArray = []
|
|
4
|
+
constructor() {
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
writeType(type) {
|
|
8
|
+
this.#outputArray.push(type)
|
|
9
|
+
return this
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
writeSize(size) {
|
|
13
|
+
this.#outputArray.push(size.toFixed())
|
|
14
|
+
return this
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
writeKey(key) {
|
|
18
|
+
this.#outputArray.push(key.length.toFixed())
|
|
19
|
+
this.#outputArray.push(key)
|
|
20
|
+
return this
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
writeString(str) {
|
|
24
|
+
this.#outputArray.push(str.length.toFixed())
|
|
25
|
+
this.#outputArray.push(str)
|
|
26
|
+
return this
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
writeNumber(num) {
|
|
30
|
+
this.#outputArray.push(num.toString())
|
|
31
|
+
return this
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
writeBoolean(bool) {
|
|
35
|
+
this.#outputArray.push(bool ? 'true' : 'false')
|
|
36
|
+
return this
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getOutput() {
|
|
40
|
+
return this.#outputArray.join(';')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class StringReader {
|
|
46
|
+
#data
|
|
47
|
+
#dataPointer = 0
|
|
48
|
+
constructor(serialized) {
|
|
49
|
+
this.#data = serialized
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
readToken() {
|
|
53
|
+
const nextSeparator = this.#data.indexOf(';', this.#dataPointer)
|
|
54
|
+
const token = this.#data.slice(this.#dataPointer, nextSeparator === -1 ? this.#data.length : nextSeparator)
|
|
55
|
+
this.#dataPointer = nextSeparator + 1
|
|
56
|
+
return token
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
readType() {
|
|
60
|
+
return this.readToken()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
readKey() {
|
|
64
|
+
const keyLength = parseInt(this.readToken())
|
|
65
|
+
const key = this.#data.slice(this.#dataPointer, this.#dataPointer + keyLength)
|
|
66
|
+
this.#dataPointer = this.#dataPointer + keyLength + 1
|
|
67
|
+
return key
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
readSize() {
|
|
71
|
+
const size = parseInt(this.readToken())
|
|
72
|
+
return size
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
readString() {
|
|
76
|
+
const size = parseInt(this.readToken())
|
|
77
|
+
const str = this.#data.slice(this.#dataPointer, this.#dataPointer + size)
|
|
78
|
+
this.#dataPointer = this.#dataPointer + size + 1
|
|
79
|
+
return str
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
readNumber() {
|
|
83
|
+
const num = parseFloat(this.readToken())
|
|
84
|
+
return num
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
readBoolean() {
|
|
88
|
+
const bool = this.readToken() === 'true'
|
|
89
|
+
return bool
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
import { write, read } from './serialization.js'
|
|
94
|
+
|
|
95
|
+
export function serializeToString(key) {
|
|
96
|
+
const writer = new StringWriter()
|
|
97
|
+
write(key, writer)
|
|
98
|
+
return writer.getOutput()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function deserializeFromString(serialized, structure) {
|
|
102
|
+
const reader = new StringReader(serialized, structure)
|
|
103
|
+
return read(reader)
|
|
104
|
+
}
|
package/stringKey.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { StringWriter, StringReader } from './string.js'
|
|
2
|
+
|
|
3
|
+
export class StringKeyWriter {
|
|
4
|
+
// private variable for data buffer, data must be sortable in the same order as keys
|
|
5
|
+
#data = []
|
|
6
|
+
// private variable for structure buffer, structure will be added to the end of the data buffer
|
|
7
|
+
#structureWriter = new StringWriter()
|
|
8
|
+
#separator = '\x00'
|
|
9
|
+
constructor(separator) {
|
|
10
|
+
if(separator) this.#separator = separator
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
writeType(type) {
|
|
14
|
+
this.#structureWriter.writeType(type)
|
|
15
|
+
return this
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
writeSize(size) {
|
|
19
|
+
this.#structureWriter.writeSize(size)
|
|
20
|
+
return this
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
writeKey(key) {
|
|
24
|
+
this.#structureWriter.writeKey(key)
|
|
25
|
+
return this
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
writeString(str) {
|
|
29
|
+
this.#structureWriter.writeSize(str.length)
|
|
30
|
+
this.#data.push(str)
|
|
31
|
+
return this
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* writeInteger(num) {
|
|
35
|
+
const numString = num.toString()
|
|
36
|
+
const numKey = `${numString.length}:${numString}`
|
|
37
|
+
this.#structureWriter.writeSize(numString.length)
|
|
38
|
+
this.#data.push(numKey)
|
|
39
|
+
return this
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
writeFloat(num) {
|
|
43
|
+
const numString = num.toString()
|
|
44
|
+
const numKey = `${numString.length}:${numString}`
|
|
45
|
+
this.#structureWriter.writeSize(numString.length)
|
|
46
|
+
this.#data.push(numKey)
|
|
47
|
+
return this
|
|
48
|
+
} */
|
|
49
|
+
|
|
50
|
+
writeNumber(num) {
|
|
51
|
+
const exponentialString = num.toExponential()
|
|
52
|
+
const [mantisaString, exponentString] = exponentialString.split('e')
|
|
53
|
+
|
|
54
|
+
const mantisa = +mantisaString
|
|
55
|
+
const negative = mantisa < 0
|
|
56
|
+
const mantisaNumbers = mantisaString.slice(negative ? 1 : 0).replace('.', '')
|
|
57
|
+
const mantisaEncoded = negative
|
|
58
|
+
? (Math.pow(10, mantisaNumbers.length) - (+mantisaNumbers))
|
|
59
|
+
: mantisaNumbers
|
|
60
|
+
|
|
61
|
+
const exponent = +exponentString
|
|
62
|
+
const negativeExponent = exponent < 0
|
|
63
|
+
const negativeEncoded = negative ^ negativeExponent
|
|
64
|
+
const exponentEncoded = negativeEncoded ? 9999 - Math.abs(exponent) : Math.abs(exponent) + 1
|
|
65
|
+
const digitizedExponent = exponentEncoded.toString(10).padStart(4, '0')
|
|
66
|
+
|
|
67
|
+
const prefix = (negativeEncoded ? '/' : '') + digitizedExponent
|
|
68
|
+
const numKey = `${negative ? '-' : ''}${prefix}v${mantisaEncoded}`
|
|
69
|
+
this.#structureWriter.writeSize(numKey.length)
|
|
70
|
+
this.#data.push(numKey)
|
|
71
|
+
return this
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
writeBoolean(bool) {
|
|
75
|
+
this.#data.push(bool ? 'true' : 'false')
|
|
76
|
+
return this
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
writeNull() {
|
|
80
|
+
return this
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getOutput() {
|
|
84
|
+
//console.log('data', this.#data)
|
|
85
|
+
//console.log('structure', this.#structureWriter.getOutput())
|
|
86
|
+
const structure = this.#structureWriter.getOutput()
|
|
87
|
+
return this.#data.join(this.#separator) + this.#separator + structure + ':' + structure.length
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getStructure() {
|
|
91
|
+
return this.#structureWriter.getOutput()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getData() {
|
|
95
|
+
return this.#data.join(this.#separator)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class StringKeyReader {
|
|
101
|
+
#data
|
|
102
|
+
#dataPointer = 0
|
|
103
|
+
#structureReader
|
|
104
|
+
|
|
105
|
+
constructor(serialized) {
|
|
106
|
+
const lastSeparator = serialized.lastIndexOf(':')
|
|
107
|
+
const structureLength = parseInt(serialized.slice(lastSeparator + 1))
|
|
108
|
+
const structure = serialized.slice(lastSeparator - structureLength, lastSeparator)
|
|
109
|
+
this.#data = serialized.slice(0, lastSeparator - structureLength - 1)
|
|
110
|
+
this.#structureReader = new StringReader(structure)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
readType() {
|
|
114
|
+
return this.#structureReader.readType()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
readSize() {
|
|
118
|
+
return this.#structureReader.readSize()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
readKey() {
|
|
122
|
+
return this.#structureReader.readKey()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
readNull() {
|
|
126
|
+
return null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
readString() {
|
|
130
|
+
const size = this.#structureReader.readSize()
|
|
131
|
+
const result = this.#data.slice(this.#dataPointer, this.#dataPointer + size)
|
|
132
|
+
this.#dataPointer += size + 1
|
|
133
|
+
return result
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
readNumber() {
|
|
137
|
+
const size = this.#structureReader.readSize()
|
|
138
|
+
const numKey = this.#data.slice(this.#dataPointer, this.#dataPointer + size)
|
|
139
|
+
this.#dataPointer += size + 1
|
|
140
|
+
|
|
141
|
+
const [prefix, encodedMantisa] = numKey.split('v')
|
|
142
|
+
const negative = prefix[0] === '-'
|
|
143
|
+
const encodedExponentString = prefix.slice(negative ? 1 : 0)
|
|
144
|
+
const negativeEncoded = encodedExponentString[0] == '/'
|
|
145
|
+
const encodedExponent = +encodedExponentString.slice(negativeEncoded ? 1 : 0)
|
|
146
|
+
const absoluteExponent = negativeEncoded ? (9999 - encodedExponent) : encodedExponent - 1
|
|
147
|
+
const negativeExponent = negativeEncoded ^ negative
|
|
148
|
+
const exponent = negativeExponent ? -absoluteExponent : absoluteExponent
|
|
149
|
+
|
|
150
|
+
const mantisaInteger = negative
|
|
151
|
+
? ''+(Math.pow(10, encodedMantisa.length) - (+encodedMantisa))
|
|
152
|
+
: ''+encodedMantisa
|
|
153
|
+
|
|
154
|
+
const mantisa = mantisaInteger.length > 1
|
|
155
|
+
? `${mantisaInteger[0]}.${mantisaInteger.slice(1)}`
|
|
156
|
+
: mantisaInteger
|
|
157
|
+
const exponentialString = `${negative ? '-' : ''}${mantisa}e${exponent}`
|
|
158
|
+
|
|
159
|
+
return +exponentialString
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
readBoolean() {
|
|
163
|
+
const result = this.#data.slice(this.#dataPointer, this.#dataPointer + 4)
|
|
164
|
+
this.#dataPointer += 5
|
|
165
|
+
return result === 'true'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
import { write, read } from './serialization.js'
|
|
170
|
+
|
|
171
|
+
export function serializeKeyToString(key) {
|
|
172
|
+
const writer = new StringKeyWriter()
|
|
173
|
+
write(key, writer)
|
|
174
|
+
return writer.getOutput()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function deserializeKeyFromString(serialized, structure) {
|
|
178
|
+
const reader = new StringKeyReader(serialized, structure)
|
|
179
|
+
return read(reader)
|
|
180
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert'
|
|
3
|
+
import { serializeKeyToString, deserializeKeyFromString } from '../stringKey.js'
|
|
4
|
+
|
|
5
|
+
function testSerializeToString(data) {
|
|
6
|
+
const serialized = serializeKeyToString(data)
|
|
7
|
+
console.log('serialized', serialized)
|
|
8
|
+
const deserialized = deserializeKeyFromString(serialized)
|
|
9
|
+
console.log('deserialized', deserialized)
|
|
10
|
+
assert.deepStrictEqual(deserialized, data)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function deepCompare(a, b) {
|
|
14
|
+
if(typeof a !== typeof b) return 'different'
|
|
15
|
+
if(typeof a === 'object') {
|
|
16
|
+
if(Array.isArray(a) && Array.isArray(b)) {
|
|
17
|
+
const maxLength = Math.max(a.length, b.length)
|
|
18
|
+
for(let i = 0; i < maxLength; i++) {
|
|
19
|
+
const av = a[i]
|
|
20
|
+
const bv = b[i]
|
|
21
|
+
if(av === undefined || bv === undefined) return 'different'
|
|
22
|
+
const result = deepCompare(a[i], b[i])
|
|
23
|
+
if(result !== 'equal') return result
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for(let key in b) {
|
|
27
|
+
const av = a[key]
|
|
28
|
+
const bv = b[key]
|
|
29
|
+
if(av === undefined || bv === undefined) return 'different'
|
|
30
|
+
}
|
|
31
|
+
for(let key in a) {
|
|
32
|
+
const av = a[key]
|
|
33
|
+
const bv = b[key]
|
|
34
|
+
if(av === undefined || bv === undefined) return 'different'
|
|
35
|
+
|
|
36
|
+
const result = deepCompare(av, bv)
|
|
37
|
+
if(result !== 'equal') return result
|
|
38
|
+
}
|
|
39
|
+
return 'equal'
|
|
40
|
+
}
|
|
41
|
+
if(a === b) return 'equal'
|
|
42
|
+
if(a > b) return 'greater'
|
|
43
|
+
if(a < b) return 'less'
|
|
44
|
+
return 'different'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function testCompareSerialization(a, b) {
|
|
48
|
+
const serializedA = serializeKeyToString(a)
|
|
49
|
+
const serializedB = serializeKeyToString(b)
|
|
50
|
+
const comparison = deepCompare(a, b)
|
|
51
|
+
const serializedComparison = deepCompare(serializedA, serializedB)
|
|
52
|
+
if(comparison === 'different') {
|
|
53
|
+
return // ignore different values
|
|
54
|
+
}
|
|
55
|
+
if(comparison !== serializedComparison) {
|
|
56
|
+
console.log("Compare Failed", JSON.stringify(a), JSON.stringify(b), '=>', comparison, '!=',
|
|
57
|
+
serializedComparison, JSON.stringify(serializedA), JSON.stringify(serializedB))
|
|
58
|
+
}
|
|
59
|
+
assert.deepStrictEqual(comparison, serializedComparison)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe('stringKey serialization', () => {
|
|
63
|
+
it('should serialize and deserialize numbers', () => {
|
|
64
|
+
testSerializeToString(1)
|
|
65
|
+
testSerializeToString(1.1)
|
|
66
|
+
testSerializeToString(1.11)
|
|
67
|
+
testSerializeToString(0.001)
|
|
68
|
+
testSerializeToString(10000)
|
|
69
|
+
testSerializeToString(-1)
|
|
70
|
+
testSerializeToString(-1.1)
|
|
71
|
+
testSerializeToString(-1.11)
|
|
72
|
+
testSerializeToString(-0.001)
|
|
73
|
+
testSerializeToString(-10000)
|
|
74
|
+
})
|
|
75
|
+
it('should serialize and deserialize strings', () => {
|
|
76
|
+
testSerializeToString('')
|
|
77
|
+
testSerializeToString('a')
|
|
78
|
+
testSerializeToString('ab')
|
|
79
|
+
testSerializeToString('abc')
|
|
80
|
+
testSerializeToString('abcd')
|
|
81
|
+
testSerializeToString('abcde')
|
|
82
|
+
testSerializeToString('abcdef')
|
|
83
|
+
testSerializeToString('a\n\r')
|
|
84
|
+
testSerializeToString('a{}\t')
|
|
85
|
+
})
|
|
86
|
+
it('should serialize and deserialize booleans', () => {
|
|
87
|
+
testSerializeToString(true)
|
|
88
|
+
testSerializeToString(false)
|
|
89
|
+
})
|
|
90
|
+
it('should serialize and deserialize null', () => {
|
|
91
|
+
testSerializeToString(null)
|
|
92
|
+
})
|
|
93
|
+
it('should serialize and deserialize objects', () => {
|
|
94
|
+
testSerializeToString({ a: 1, c: 'a', b: 2 })
|
|
95
|
+
})
|
|
96
|
+
it('should serialize and deserialize arrays', () => {
|
|
97
|
+
testSerializeToString([1, 2, 3])
|
|
98
|
+
testSerializeToString(['a', 'b', 'c'])
|
|
99
|
+
testSerializeToString([true, false, null])
|
|
100
|
+
testSerializeToString([{ a: 1, c: 'a', b: 2 }, { a: 3, b: 4 }])
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('should leave numbers in the same order', () => {
|
|
104
|
+
const numers = [1, 2, 1.1, 1.11, 0.001, 10000, 10000.1, -1, -1.1, -1.11, -0.001, -10000, -10000.1]
|
|
105
|
+
for(let i = 0; i < numers.length; i++) {
|
|
106
|
+
for(let j = i + 1; j < numers.length; j++) {
|
|
107
|
+
testCompareSerialization(numers[i], numers[j])
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
it('should leave strings in the same order', () => {
|
|
112
|
+
const strings = ['', 'a', 'ab', 'abc', 'abcd', 'abcde', 'abcdef', 'a\n\r', 'a{}\t']
|
|
113
|
+
for(let i = 0; i < strings.length; i++) {
|
|
114
|
+
for(let j = i + 1; j < strings.length; j++) {
|
|
115
|
+
testCompareSerialization(strings[i], strings[j])
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
it('should leave booleans in the same order', () => {
|
|
120
|
+
const booleans = [true, false]
|
|
121
|
+
for(let i = 0; i < booleans.length; i++) {
|
|
122
|
+
for(let j = i + 1; j < booleans.length; j++) {
|
|
123
|
+
testCompareSerialization(booleans[i], booleans[j])
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
it('should leave arrays in the same order', () => {
|
|
128
|
+
const arrays = [[], [1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
|
|
129
|
+
for(let i = 0; i < arrays.length; i++) {
|
|
130
|
+
for(let j = i + 1; j < arrays.length; j++) {
|
|
131
|
+
testCompareSerialization(arrays[i], arrays[j])
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
it('should leave objects in the same order', () => {
|
|
136
|
+
const objects = [{}, {a: 1}, {a: 1, b: 2}, {a: 1, b: 2, c: 3}, {a:2, b: 1}, {a: 2}, {a:1, b:1}]
|
|
137
|
+
for(let i = 0; i < objects.length; i++) {
|
|
138
|
+
for(let j = i + 1; j < objects.length; j++) {
|
|
139
|
+
testCompareSerialization(objects[i], objects[j])
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
})
|