jsonbinpack
Version:
A space-efficient schema-driven binary JSON serialization format based on JSON Schema
336 lines (285 loc) • 10.4 kB
text/typescript
/*
* Copyright 2021 Juan Cruz Viotti
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
strict as assert
} from 'assert'
import ResizableBuffer from '../resizable-buffer'
import {
BitsetResult,
bitsetDecode
} from './bitset'
import {
IntegerListResult,
integerListDecode
} from './integer-list'
import {
JSONObject
} from '../../json'
import {
IntegerResult,
FLOOR_MULTIPLE_ENUM_VARINT
} from '../integer/decode'
import {
BoundedTypedOptions,
RequiredUnboundedTypedOptions,
OptionalUnboundedTypedOptions,
UnboundedTypedOptions,
TypedKeysOptions,
OptionalBoundedTypedOptions,
RequiredBoundedTypedOptions,
PackedUnboundedOptions,
PackedRequiredBoundedOptions,
SizeTypedKeysOptions
} from './options'
import {
decode,
DecodeResult
} from '../index'
import {
Encoding
} from '../../mapper'
export interface ObjectResult extends DecodeResult {
readonly value: JSONObject;
readonly bytes: number;
}
export const REQUIRED_ONLY_BOUNDED_TYPED_OBJECT = (
buffer: ResizableBuffer, offset: number, options: RequiredBoundedTypedOptions
): ObjectResult => {
const result: JSONObject = {}
const booleanResult: BitsetResult =
bitsetDecode(buffer, offset, options.booleanRequiredProperties.length)
for (const [ index, bit ] of booleanResult.value.entries()) {
Reflect.set(result, options.booleanRequiredProperties[index], bit)
}
let cursor: number = offset + booleanResult.bytes
for (const key of options.requiredProperties) {
const encoding: Encoding | undefined = options.propertyEncodings[key]
assert(typeof encoding !== 'undefined')
const propertyResult: DecodeResult = decode(buffer, cursor, encoding)
assert(propertyResult.bytes >= 0)
Reflect.set(result, key, propertyResult.value)
cursor += propertyResult.bytes
}
return {
value: result,
bytes: cursor - offset
}
}
export const NON_REQUIRED_BOUNDED_TYPED_OBJECT = (
buffer: ResizableBuffer, offset: number, options: OptionalBoundedTypedOptions
): ObjectResult => {
const bitsetLength: IntegerResult = FLOOR_MULTIPLE_ENUM_VARINT(buffer, offset, {
minimum: 0,
multiplier: 1
})
assert(bitsetLength.value >= 0)
const bitsetResult: BitsetResult = bitsetDecode(
buffer, offset + bitsetLength.bytes, bitsetLength.value)
assert(bitsetResult.value.length === bitsetLength.value)
const result: JSONObject = {}
let cursor: number = offset + bitsetLength.bytes + bitsetResult.bytes
for (const [ index, value ] of bitsetResult.value.entries()) {
if (!value) {
continue
}
const key: string = options.optionalProperties[index]
const encoding: Encoding | undefined = options.propertyEncodings[key]
assert(typeof encoding !== 'undefined')
const propertyResult: DecodeResult = decode(buffer, cursor, encoding)
assert(propertyResult.bytes >= 0)
Reflect.set(result, key, propertyResult.value)
cursor += propertyResult.bytes
}
return {
value: result,
bytes: cursor - offset
}
}
export const MIXED_BOUNDED_TYPED_OBJECT = (
buffer: ResizableBuffer, offset: number, options: BoundedTypedOptions
): ObjectResult => {
const requiredResult: ObjectResult = REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, offset, {
propertyEncodings: options.propertyEncodings,
requiredProperties: options.requiredProperties,
booleanRequiredProperties: options.booleanRequiredProperties
})
const optionalResult: ObjectResult = NON_REQUIRED_BOUNDED_TYPED_OBJECT(
buffer, offset + requiredResult.bytes, {
propertyEncodings: options.propertyEncodings,
optionalProperties: options.optionalProperties
})
return {
bytes: requiredResult.bytes + optionalResult.bytes,
value: Object.assign(requiredResult.value, optionalResult.value)
}
}
export const ARBITRARY_TYPED_KEYS_OBJECT_WITHOUT_LENGTH = (
buffer: ResizableBuffer, offset: number, options: SizeTypedKeysOptions
): ObjectResult => {
assert(options.size >= 0)
let count: number = 0
let cursor: number = offset
const value: JSONObject = {}
while (count < options.size) {
const keyResult: DecodeResult =
decode(buffer, cursor, options.keyEncoding)
assert(typeof keyResult.value === 'string')
assert(keyResult.bytes >= 0)
cursor += keyResult.bytes
const valueResult: DecodeResult = decode(buffer, cursor, options.encoding)
assert(valueResult.bytes >= 0)
cursor += valueResult.bytes
Reflect.set(value, keyResult.value, valueResult.value)
count += 1
}
return {
value,
bytes: cursor - offset
}
}
export const ARBITRARY_TYPED_KEYS_OBJECT = (
buffer: ResizableBuffer, offset: number, options: TypedKeysOptions
): ObjectResult => {
const sizeResult: IntegerResult = FLOOR_MULTIPLE_ENUM_VARINT(buffer, offset, {
minimum: 0,
multiplier: 1
})
const result: ObjectResult = ARBITRARY_TYPED_KEYS_OBJECT_WITHOUT_LENGTH(
buffer, offset + sizeResult.bytes, {
encoding: options.encoding,
keyEncoding: options.keyEncoding,
size: sizeResult.value
})
return {
bytes: sizeResult.bytes + result.bytes,
value: result.value
}
}
export const REQUIRED_UNBOUNDED_TYPED_OBJECT = (
buffer: ResizableBuffer, offset: number, options: RequiredUnboundedTypedOptions
): ObjectResult => {
const requiredResult: ObjectResult = REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, offset, {
propertyEncodings: options.propertyEncodings,
requiredProperties: options.requiredProperties,
booleanRequiredProperties: options.booleanRequiredProperties
})
const arbitraryResult: ObjectResult = ARBITRARY_TYPED_KEYS_OBJECT(
buffer, offset + requiredResult.bytes, {
keyEncoding: options.keyEncoding,
encoding: options.encoding
})
return {
bytes: requiredResult.bytes + arbitraryResult.bytes,
value: Object.assign(requiredResult.value, arbitraryResult.value)
}
}
export const OPTIONAL_UNBOUNDED_TYPED_OBJECT = (
buffer: ResizableBuffer, offset: number, options: OptionalUnboundedTypedOptions
): ObjectResult => {
const optionalResult: ObjectResult = NON_REQUIRED_BOUNDED_TYPED_OBJECT(
buffer, offset, {
propertyEncodings: options.propertyEncodings,
optionalProperties: options.optionalProperties
})
const arbitraryResult: ObjectResult = ARBITRARY_TYPED_KEYS_OBJECT(
buffer, offset + optionalResult.bytes, {
keyEncoding: options.keyEncoding,
encoding: options.encoding
})
return {
bytes: optionalResult.bytes + arbitraryResult.bytes,
value: Object.assign(optionalResult.value, arbitraryResult.value)
}
}
export const MIXED_UNBOUNDED_TYPED_OBJECT = (
buffer: ResizableBuffer, offset: number, options: UnboundedTypedOptions
): ObjectResult => {
const requiredResult: ObjectResult = REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, offset, {
propertyEncodings: options.propertyEncodings,
requiredProperties: options.requiredProperties,
booleanRequiredProperties: options.booleanRequiredProperties
})
const optionalResult: ObjectResult = NON_REQUIRED_BOUNDED_TYPED_OBJECT(
buffer, offset + requiredResult.bytes, {
propertyEncodings: options.propertyEncodings,
optionalProperties: options.optionalProperties
})
const arbitraryResult: ObjectResult = ARBITRARY_TYPED_KEYS_OBJECT(
buffer, offset + requiredResult.bytes + optionalResult.bytes, {
keyEncoding: options.keyEncoding,
encoding: options.encoding
})
return {
bytes: requiredResult.bytes + optionalResult.bytes + arbitraryResult.bytes,
value: Object.assign(requiredResult.value, optionalResult.value, arbitraryResult.value)
}
}
export const PACKED_UNBOUNDED_OBJECT = (
buffer: ResizableBuffer, offset: number, options: PackedUnboundedOptions
): ObjectResult => {
const packedLength: IntegerResult = FLOOR_MULTIPLE_ENUM_VARINT(buffer, offset, {
minimum: 0,
multiplier: 1
})
const packedResult: IntegerListResult = integerListDecode(
buffer, offset + packedLength.bytes, packedLength.value, {
minimum: options.packedEncoding.options.minimum,
maximum: options.packedEncoding.options.maximum
})
const result: JSONObject = {}
for (const [ index, key ] of options.packedRequiredProperties.entries()) {
Reflect.set(result, key, packedResult.value[index])
}
const rest: ObjectResult = MIXED_UNBOUNDED_TYPED_OBJECT(
buffer, offset + packedLength.bytes + packedResult.bytes, {
requiredProperties: options.requiredProperties,
booleanRequiredProperties: options.booleanRequiredProperties,
optionalProperties: options.optionalProperties,
keyEncoding: options.keyEncoding,
encoding: options.encoding,
propertyEncodings: options.propertyEncodings
})
return {
value: Object.assign(result, rest.value),
bytes: packedLength.bytes + packedResult.bytes + rest.bytes
}
}
export const PACKED_BOUNDED_REQUIRED_OBJECT = (
buffer: ResizableBuffer, offset: number, options: PackedRequiredBoundedOptions
): ObjectResult => {
const packedResult: IntegerListResult = integerListDecode(
buffer, offset, options.packedRequiredProperties.length, {
minimum: options.packedEncoding.options.minimum,
maximum: options.packedEncoding.options.maximum
})
const result: JSONObject = {}
for (const [ index, key ] of options.packedRequiredProperties.entries()) {
Reflect.set(result, key, packedResult.value[index])
}
const rest: ObjectResult = REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, offset + packedResult.bytes, {
propertyEncodings: options.propertyEncodings,
requiredProperties: options.requiredProperties,
booleanRequiredProperties: options.booleanRequiredProperties
})
return {
value: Object.assign(result, rest.value),
bytes: packedResult.bytes + rest.bytes
}
}