jsonbinpack
Version:
A space-efficient schema-driven binary JSON serialization format based on JSON Schema
165 lines (142 loc) • 4.93 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 {
range
} from 'lodash'
import {
JSONValue
} from '../json'
import {
BaseEncodingDefinition
} from './base-encoding-definition'
import {
EncodingType
} from '../encoder'
import {
UINT8_MAX
} from '../utils/limits'
import {
FloorMultiplierOptions,
RoofMultiplierOptions,
MultiplierOptions,
BoundedMultiplierOptions
} from '../encoder/integer/options'
import {
IntegerEncodingSchema
} from '../schema'
export interface BOUNDED_MULTIPLE_8BITS_ENUM_FIXED_ENCODING extends BaseEncodingDefinition {
readonly type: EncodingType.Integer;
readonly encoding: 'BOUNDED_MULTIPLE_8BITS_ENUM_FIXED';
readonly options: BoundedMultiplierOptions;
}
export interface FLOOR_MULTIPLE_ENUM_VARINT_ENCODING extends BaseEncodingDefinition {
readonly type: EncodingType.Integer;
readonly encoding: 'FLOOR_MULTIPLE_ENUM_VARINT';
readonly options: FloorMultiplierOptions;
}
export interface ROOF_MULTIPLE_MIRROR_ENUM_VARINT_ENCODING extends BaseEncodingDefinition {
readonly type: EncodingType.Integer;
readonly encoding: 'ROOF_MULTIPLE_MIRROR_ENUM_VARINT';
readonly options: RoofMultiplierOptions;
}
export interface ARBITRARY_MULTIPLE_ZIGZAG_VARINT_ENCODING extends BaseEncodingDefinition {
readonly type: EncodingType.Integer;
readonly encoding: 'ARBITRARY_MULTIPLE_ZIGZAG_VARINT';
readonly options: MultiplierOptions;
}
export type IntegerEncodingNames =
'BOUNDED_MULTIPLE_8BITS_ENUM_FIXED' |
'FLOOR_MULTIPLE_ENUM_VARINT' |
'ROOF_MULTIPLE_MIRROR_ENUM_VARINT' |
'ARBITRARY_MULTIPLE_ZIGZAG_VARINT'
export type IntegerEncoding =
BOUNDED_MULTIPLE_8BITS_ENUM_FIXED_ENCODING |
FLOOR_MULTIPLE_ENUM_VARINT_ENCODING |
ROOF_MULTIPLE_MIRROR_ENUM_VARINT_ENCODING |
ARBITRARY_MULTIPLE_ZIGZAG_VARINT_ENCODING
export const getIntegerStates = (schema: IntegerEncodingSchema): number | JSONValue[] => {
if (typeof schema.maximum === 'number' &&
typeof schema.minimum === 'number') {
// TODO: De-duplicate this logic on encoder/integer/encode
const absoluteMultiplier: number = Math.abs(schema.multipleOf ?? 1)
const enumMinimum: number = Math.ceil(schema.minimum / absoluteMultiplier)
const enumMaximum: number = Math.floor(schema.maximum / absoluteMultiplier)
// It is pointless to calculate the exact amount of states after a certain point
if (enumMaximum - enumMinimum > UINT8_MAX) {
return enumMaximum - enumMinimum + 1
}
return range(enumMinimum, enumMaximum + 1).map((value: number) => {
return value * absoluteMultiplier
})
}
return Infinity
}
export const getIntegerEncoding = (schema: IntegerEncodingSchema, _level: number): IntegerEncoding => {
assert(typeof schema.minimum === 'undefined' ||
typeof schema.maximum === 'undefined' ||
schema.maximum >= schema.minimum)
const states: number | JSONValue[] = getIntegerStates(schema)
const statesLength = typeof states === 'number' ? states : states.length
if (typeof schema.minimum !== 'undefined' && typeof schema.maximum !== 'undefined') {
if (statesLength <= UINT8_MAX) {
return {
type: EncodingType.Integer,
encoding: 'BOUNDED_MULTIPLE_8BITS_ENUM_FIXED',
options: {
minimum: schema.minimum,
maximum: schema.maximum,
multiplier: schema.multipleOf ?? 1
}
}
}
return {
type: EncodingType.Integer,
encoding: 'FLOOR_MULTIPLE_ENUM_VARINT',
options: {
minimum: schema.minimum,
multiplier: schema.multipleOf ?? 1
}
}
} else if (typeof schema.minimum !== 'undefined' && typeof schema.maximum === 'undefined') {
return {
type: EncodingType.Integer,
encoding: 'FLOOR_MULTIPLE_ENUM_VARINT',
options: {
minimum: schema.minimum,
multiplier: schema.multipleOf ?? 1
}
}
} else if (typeof schema.minimum === 'undefined' && typeof schema.maximum !== 'undefined') {
return {
type: EncodingType.Integer,
encoding: 'ROOF_MULTIPLE_MIRROR_ENUM_VARINT',
options: {
maximum: schema.maximum,
multiplier: schema.multipleOf ?? 1
}
}
}
return {
type: EncodingType.Integer,
encoding: 'ARBITRARY_MULTIPLE_ZIGZAG_VARINT',
options: {
multiplier: schema.multipleOf ?? 1
}
}
}