jsonbinpack
Version:
A space-efficient schema-driven binary JSON serialization format based on JSON Schema
156 lines (131 loc) • 4.79 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 {
JSONValue
} from '../../json'
import {
UINT8_MAX
} from '../../utils/limits'
import {
encode
} from '../index'
import {
Encoding
} from '../../mapper'
import {
TypedBoundedOptions,
TypedFloorOptions,
TypedRoofOptions,
SizeTypedOptions
} from './options'
import {
FLOOR_MULTIPLE_ENUM_VARINT,
BOUNDED_MULTIPLE_8BITS_ENUM_FIXED,
ROOF_MULTIPLE_MIRROR_ENUM_VARINT
} from '../integer/encode'
import {
EncodingContext
} from '../context'
const encodeArray = (
buffer: ResizableBuffer, offset: number, value: JSONValue[],
prefixEncodings: Encoding[], context: EncodingContext, defaultEncoding: Encoding
): number => {
let cursor = offset
for (const [ index, element ] of value.entries()) {
const encoding: Encoding | undefined = prefixEncodings[index] ?? defaultEncoding
const bytesWritten: number =
encode(buffer, cursor, encoding, element, context)
cursor += bytesWritten
}
return cursor - offset
}
export const FIXED_TYPED_ARRAY = (
buffer: ResizableBuffer, offset: number, value: JSONValue[],
options: SizeTypedOptions, context: EncodingContext
): number => {
assert(options.size >= 0)
return encodeArray(buffer, offset, value, options.prefixEncodings, context, options.encoding)
}
export const BOUNDED_TYPED_LENGTH_PREFIX = (
buffer: ResizableBuffer, offset: number, value: JSONValue[], options: TypedBoundedOptions, context: EncodingContext
): number => {
assert(options.maximum >= 0)
assert(options.minimum >= 0)
assert(options.maximum >= options.minimum)
assert(value.length >= options.minimum)
assert(value.length <= options.maximum)
// TODO: Map cases where maximum == minimum to FIXED_TYPED_ARRAY in the mapper
// instead of handling it here
const lengthBytes: number = options.maximum === options.minimum
? 0
: FLOOR_MULTIPLE_ENUM_VARINT(buffer, offset, value.length, {
minimum: options.minimum,
multiplier: 1
}, context)
return lengthBytes + encodeArray(
buffer, offset + lengthBytes, value, options.prefixEncodings, context, options.encoding)
}
export const BOUNDED_8BITS_TYPED_LENGTH_PREFIX = (
buffer: ResizableBuffer, offset: number, value: JSONValue[], options: TypedBoundedOptions, context: EncodingContext
): number => {
assert(options.maximum >= 0)
assert(options.minimum >= 0)
assert(options.maximum >= options.minimum)
assert(value.length >= options.minimum)
assert(value.length <= options.maximum)
assert(options.maximum - options.minimum <= UINT8_MAX)
// TODO: Map cases where maximum == minimum to FIXED_TYPED_ARRAY in the mapper
// instead of handling it here
const lengthBytes: number = options.maximum === options.minimum
? 0
: BOUNDED_MULTIPLE_8BITS_ENUM_FIXED(buffer, offset, value.length, {
minimum: options.minimum,
maximum: options.maximum,
multiplier: 1
}, context)
return lengthBytes + encodeArray(
buffer, offset + lengthBytes, value, options.prefixEncodings, context, options.encoding)
}
export const ROOF_TYPED_LENGTH_PREFIX = (
buffer: ResizableBuffer, offset: number, value: JSONValue[], options: TypedRoofOptions, context: EncodingContext
): number => {
assert(options.maximum >= 0)
assert(value.length <= options.maximum)
const lengthBytes: number =
ROOF_MULTIPLE_MIRROR_ENUM_VARINT(buffer, offset, value.length, {
maximum: options.maximum,
multiplier: 1
}, context)
return lengthBytes + encodeArray(
buffer, offset + lengthBytes, value, options.prefixEncodings, context, options.encoding)
}
export const FLOOR_TYPED_LENGTH_PREFIX = (
buffer: ResizableBuffer, offset: number, value: JSONValue[], options: TypedFloorOptions, context: EncodingContext
): number => {
assert(options.minimum >= 0)
assert(value.length >= options.minimum)
const lengthBytes: number =
FLOOR_MULTIPLE_ENUM_VARINT(buffer, offset, value.length, {
minimum: options.minimum,
multiplier: 1
}, context)
return lengthBytes + encodeArray(
buffer, offset + lengthBytes, value, options.prefixEncodings, context, options.encoding)
}