use json.stringify/parse
This commit is contained in:
parent
7c4962691c
commit
75cbc66063
7 changed files with 1185 additions and 1534 deletions
1023
dist/index.js
vendored
Normal file
1023
dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1101
dist/test.js
vendored
1101
dist/test.js
vendored
File diff suppressed because it is too large
Load diff
18
package-lock.json
generated
18
package-lock.json
generated
|
@ -9,6 +9,7 @@
|
|||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"kuebiko": "file:../kuebiko/",
|
||||
"serde": "file:../serde-ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -16,6 +17,19 @@
|
|||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
"../kuebiko": {
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"izuna": "git+https://git.kitsu.cafe/rowan/izuna.git",
|
||||
"kojima": "git+https://git.kitsu.cafe/rowan/kojima.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.2",
|
||||
"folktest": "git+https://git.kitsu.cafe/rowan/folktest.git",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
"../serde-ts": {
|
||||
"name": "serde",
|
||||
"version": "1.0.0",
|
||||
|
@ -494,6 +508,10 @@
|
|||
"@esbuild/win32-x64": "0.25.4"
|
||||
}
|
||||
},
|
||||
"node_modules/kuebiko": {
|
||||
"resolved": "../kuebiko",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/serde": {
|
||||
"resolved": "../serde-ts",
|
||||
"link": true
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "esbuild src/index.ts --bundle --outfile=dist/index.js",
|
||||
"build:test": "esbuild test.ts --format=cjs --bundle --target=es2022 --outfile=dist/test.js --tsconfig=tsconfig.json",
|
||||
|
@ -11,7 +12,6 @@
|
|||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"serde": "file:../serde-ts"
|
||||
},
|
||||
|
|
435
src/de.ts
435
src/de.ts
|
@ -1,396 +1,135 @@
|
|||
import { GlobalRegistry, IterResult, Registry } from 'serde'
|
||||
import { IIterableAccess, MapAccess, IVisitor, IDeserializer, Deserialize, GenericSeed, Visitor } from 'serde/de'
|
||||
import { unexpected } from './err'
|
||||
import { getDeserialize, GlobalRegistry, IterResult, Registry } from 'serde'
|
||||
import { Deserialize, Forwarder, IDeserializer, IterableAccess, IVisitor, MapAccess, Visitor } from 'serde/de'
|
||||
|
||||
type Byte = number
|
||||
|
||||
const clamp = (value: number, min: number, max: number): number => {
|
||||
return Math.min(Math.max(value, min), max)
|
||||
interface Reviver {
|
||||
<T, U>(key: string, value: T): U
|
||||
}
|
||||
|
||||
const isNumeric = (value: any): value is number => {
|
||||
return !isNaN(value)
|
||||
type Entry = [any, any]
|
||||
|
||||
const unwrap = (de: IDeserializer) => de.deserializeAny(new Visitor())
|
||||
const deserializer = (registry: Registry = GlobalRegistry) => <T, U>(_key: string, value: T) => {
|
||||
const de = getDeserialize(value, unwrap, registry)
|
||||
return de(new Forwarder(value)) as U
|
||||
}
|
||||
|
||||
const isNumericToken = (value: Byte) => {
|
||||
return value === Token.Period || value === Token.Hyphen || Token.Digit.includes(value)
|
||||
const parser = (reviver: Reviver) => <T, U>(value: T): U => {
|
||||
return JSON.parse(value as any, reviver)
|
||||
}
|
||||
|
||||
interface Predicate<T> {
|
||||
(value: T): boolean
|
||||
}
|
||||
export class JSONMapAccess extends MapAccess {
|
||||
private readonly _entries: Entry[]
|
||||
private index: number = -1
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
const b = (strings: TemplateStringsArray) => encoder.encode(strings[0])
|
||||
const char = (strings: TemplateStringsArray) => b(strings)[0]
|
||||
|
||||
const Literal = Object.freeze({
|
||||
True: b`true`,
|
||||
False: b`false`
|
||||
} as const)
|
||||
|
||||
const Token = Object.freeze({
|
||||
Space: char` `,
|
||||
LeftCurly: char`{`,
|
||||
RightCurly: char`}`,
|
||||
LeftSquare: char`[`,
|
||||
RightSquare: char`]`,
|
||||
Quote: char`"`,
|
||||
ForwardSlash: char`\\`,
|
||||
Digit: b`0123456789`,
|
||||
Hyphen: char`-`,
|
||||
Period: char`.`,
|
||||
Comma: char`,`,
|
||||
Colon: char`:`
|
||||
} as const)
|
||||
|
||||
export class CommaSeparated<T> extends MapAccess implements IIterableAccess {
|
||||
private readonly de: JSONDeserializer
|
||||
private readonly defaultSeed: GenericSeed<any>
|
||||
private first: boolean = true
|
||||
|
||||
constructor(deserializer: JSONDeserializer, visitor: IVisitor<T> = new Visitor()) {
|
||||
constructor(entries: Entry[]) {
|
||||
super()
|
||||
this.de = deserializer
|
||||
this.defaultSeed = new GenericSeed(visitor)
|
||||
|
||||
this._entries = entries
|
||||
}
|
||||
|
||||
private seed<V>(): Deserialize<V> {
|
||||
return this.defaultSeed.deserialize.bind(this.defaultSeed) as Deserialize<V>
|
||||
static fromObject(value: object) {
|
||||
return new this(Object.entries(value))
|
||||
}
|
||||
|
||||
private nextItemSeed<T, D extends Deserialize<T>>(seed: D, end: number): IteratorResult<T> {
|
||||
if (this.de.buffer.peek().next() === end) {
|
||||
nextKey<T>(seed?: Deserialize<T>): IteratorResult<T> {
|
||||
this.index += 1
|
||||
|
||||
if (this.index >= this._entries.length) {
|
||||
return IterResult.Done()
|
||||
} else {
|
||||
const key = this._entries[this.index][0]
|
||||
const deser = seed != null ? seed(key) : key
|
||||
return IterResult.Next(deser)
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.first) {
|
||||
const take = this.de.buffer.take()
|
||||
if (take.next() !== Token.Comma) {
|
||||
throw unexpected(',', take.toString(), this.de.buffer.position)
|
||||
}
|
||||
nextValue<T>(seed?: Deserialize<T>): IteratorResult<T> {
|
||||
if (this.index >= this._entries.length) {
|
||||
return IterResult.Done()
|
||||
} else {
|
||||
const value = this._entries[this.index][1]
|
||||
const deser = seed != null ? seed(value) : value
|
||||
return IterResult.Next(deser)
|
||||
}
|
||||
|
||||
this.first = false
|
||||
return IterResult.Next(seed(this.de)) as IteratorResult<T>
|
||||
}
|
||||
|
||||
nextKeySeed<T, K extends Deserialize<T>>(seed: K): IteratorResult<T> {
|
||||
return this.nextItemSeed(seed, Token.RightCurly)
|
||||
}
|
||||
|
||||
nextValueSeed<T, V extends Deserialize<T>>(seed: V): IteratorResult<T> {
|
||||
const next = this.de.buffer.next()
|
||||
|
||||
if (next !== Token.Colon) {
|
||||
throw unexpected(':', String.fromCharCode(next), this.de.buffer.position)
|
||||
}
|
||||
|
||||
return IterResult.Next(seed(this.de)) as IteratorResult<T>
|
||||
}
|
||||
|
||||
private nextItem<T>(end: number): IteratorResult<T> {
|
||||
return this.nextItemSeed(this.seed<T>(), end)
|
||||
}
|
||||
|
||||
nextKey<T>(): IteratorResult<T> {
|
||||
return this.nextItem(Token.RightCurly)
|
||||
}
|
||||
|
||||
nextValue<V>(): IteratorResult<V> {
|
||||
return this.nextValueSeed(this.seed<V>())
|
||||
}
|
||||
|
||||
nextElement<T>(): IteratorResult<T> {
|
||||
return this.nextItem(Token.RightSquare)
|
||||
}
|
||||
}
|
||||
|
||||
class ByteArray {
|
||||
private readonly view: Uint8Array
|
||||
private readonly encoder: TextEncoder
|
||||
private readonly decoder: TextDecoder
|
||||
export class JSONIterableAccess extends IterableAccess {
|
||||
private readonly iterator: Iterator<any>
|
||||
|
||||
private index: number = 0
|
||||
|
||||
get position() {
|
||||
return this.index
|
||||
constructor(iterator: Iterator<any>) {
|
||||
super()
|
||||
this.iterator = iterator
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.view.byteLength
|
||||
static fromIterable(iterable: Iterable<any>) {
|
||||
return new this(iterable[Symbol.iterator]())
|
||||
}
|
||||
|
||||
constructor(view: Uint8Array, encoder: TextEncoder = new TextEncoder(), decoder: TextDecoder = new TextDecoder()) {
|
||||
this.view = view
|
||||
this.encoder = encoder
|
||||
this.decoder = decoder
|
||||
}
|
||||
|
||||
static fromArrayBuffer(value: ArrayBuffer, encoder?: TextEncoder, decoder?: TextDecoder): ByteArray {
|
||||
return new this(new Uint8Array(value), encoder, decoder)
|
||||
}
|
||||
|
||||
static fromString(value: string, encoder: TextEncoder = new TextEncoder(), decoder?: TextDecoder): ByteArray {
|
||||
return this.fromArrayBuffer(
|
||||
encoder.encode(value),
|
||||
encoder,
|
||||
decoder
|
||||
)
|
||||
}
|
||||
|
||||
next() {
|
||||
const value = this.view[this.index]
|
||||
this.index += 1
|
||||
return value
|
||||
}
|
||||
|
||||
nextChar() {
|
||||
return this.take().toString()
|
||||
}
|
||||
|
||||
done(): boolean {
|
||||
return this.index >= this.view.byteLength
|
||||
}
|
||||
|
||||
toBytes() {
|
||||
return this.view.slice(this.index)
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.decoder.decode(this.toBytes())
|
||||
}
|
||||
|
||||
take(limit: number = 1): ByteArray {
|
||||
const bytes = this.peek(limit)
|
||||
this.index += limit
|
||||
return bytes
|
||||
}
|
||||
|
||||
at(index: number) {
|
||||
return this.view[this.index + index]
|
||||
}
|
||||
|
||||
takeWhile(fn: Predicate<number>): ByteArray {
|
||||
let index = 0
|
||||
|
||||
while (!this.done() && fn(this.at(index))) {
|
||||
index += 1
|
||||
nextElement<T, D extends Deserialize<T>>(seed?: D): IteratorResult<T> {
|
||||
const result = this.iterator.next()
|
||||
if (result.done) {
|
||||
return IterResult.Done()
|
||||
} else {
|
||||
const value = seed != null ? seed(result.value) : result.value
|
||||
return IterResult.Next(value)
|
||||
}
|
||||
|
||||
return this.take(index)
|
||||
}
|
||||
|
||||
takeUntil(fn: Predicate<number>): ByteArray {
|
||||
return this.takeWhile((v: number) => !fn(v))
|
||||
}
|
||||
|
||||
drop(limit: number) {
|
||||
this.index += limit
|
||||
return this
|
||||
}
|
||||
|
||||
dropWhile(fn: Predicate<number>): ByteArray {
|
||||
let index = 0
|
||||
|
||||
while (!this.done() && fn(this.at(index))) {
|
||||
index += 1
|
||||
}
|
||||
|
||||
return this.drop(index)
|
||||
}
|
||||
|
||||
dropUntil(fn: Predicate<number>): ByteArray {
|
||||
return this.dropWhile((v: number) => !fn(v))
|
||||
}
|
||||
|
||||
peek(limit: number = 1): ByteArray {
|
||||
const index = this.index
|
||||
return this.slice(index, index + limit)
|
||||
}
|
||||
|
||||
startsWith(value: string | ArrayBufferLike): boolean {
|
||||
if (typeof value === 'string') {
|
||||
return this.startsWith(this.encoder.encode(value))
|
||||
}
|
||||
|
||||
const length = value.byteLength
|
||||
const bytes = new Uint8Array(value)
|
||||
|
||||
return this.peek(length).toBytes().every((v, i) => v === bytes[i])
|
||||
}
|
||||
|
||||
slice(start?: number, end?: number) {
|
||||
return new ByteArray(
|
||||
this.view.subarray(start, end),
|
||||
this.encoder,
|
||||
this.decoder
|
||||
)
|
||||
}
|
||||
|
||||
indexOf(value: number | ArrayBufferLike, start: number = 0) {
|
||||
const search = new Uint8Array(isNumeric(value) ? [value] : value)
|
||||
start = clamp(start, this.index, this.length)
|
||||
const bytes = this.slice(start)
|
||||
|
||||
for (let i = 0, len = bytes.length; i < len; i++) {
|
||||
if (bytes.at(i) === search[0] && bytes.slice(i).startsWith(search)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
export class JSONDeserializer implements IDeserializer {
|
||||
readonly buffer: ByteArray
|
||||
readonly registry: Registry
|
||||
private readonly input: string
|
||||
private readonly parser: <T, U>(value: T) => U
|
||||
|
||||
constructor(buffer: ByteArray, registry: Registry = GlobalRegistry) {
|
||||
this.buffer = buffer
|
||||
this.registry = registry
|
||||
constructor(input: string, registry: Registry = GlobalRegistry) {
|
||||
this.input = input
|
||||
this.parser = parser(deserializer(registry))
|
||||
}
|
||||
|
||||
static fromString(value: string): JSONDeserializer {
|
||||
return new this(ByteArray.fromString(value))
|
||||
static fromString(value: string, registry?: Registry) {
|
||||
return new this(value, registry)
|
||||
}
|
||||
|
||||
deserializeAny<T>(visitor: IVisitor<T>): T {
|
||||
const peek = this.buffer.peek()
|
||||
const nextByte = peek.take()
|
||||
const byte = nextByte.next()
|
||||
|
||||
switch (true) {
|
||||
case b`n`.includes(byte):
|
||||
return this.deserializeNull(visitor)
|
||||
case b`tf`.includes(byte):
|
||||
return this.deserializeBoolean(visitor)
|
||||
case b`-0123456789`.includes(byte):
|
||||
return this.deserializeNumber(visitor)
|
||||
case Token.Quote === byte:
|
||||
return this.deserializeString(visitor)
|
||||
case Token.LeftSquare === byte:
|
||||
return this.deserializeIterable(visitor)
|
||||
case Token.LeftCurly === byte:
|
||||
return this.deserializeObject(visitor)
|
||||
default:
|
||||
throw new SyntaxError(`Invalid syntax at position ${this.buffer.position}: "${nextByte.toString()}"`)
|
||||
}
|
||||
deserializeAny<T>(_visitor: IVisitor<T>): T {
|
||||
return this.parser(this.input)
|
||||
}
|
||||
|
||||
deserializeNull<T, V extends IVisitor<T>>(visitor: V): T {
|
||||
if (this.buffer.startsWith('null')) {
|
||||
this.buffer.take(4)
|
||||
}
|
||||
deserializeBoolean<T>(visitor: IVisitor<T>): T {
|
||||
return visitor.visitBoolean(this.parser(this.input))
|
||||
}
|
||||
|
||||
deserializeNumber<T>(visitor: IVisitor<T>): T {
|
||||
return visitor.visitNumber(this.parser(this.input))
|
||||
}
|
||||
|
||||
deserializeBigInt<T>(visitor: IVisitor<T>): T {
|
||||
return visitor.visitBigInt(this.parser(this.input))
|
||||
}
|
||||
|
||||
deserializeString<T>(visitor: IVisitor<T>): T {
|
||||
return visitor.visitString(this.parser(this.input))
|
||||
}
|
||||
|
||||
deserializeSymbol<T>(visitor: IVisitor<T>): T {
|
||||
return visitor.visitSymbol(this.parser(this.input))
|
||||
}
|
||||
|
||||
deserializeNull<T>(visitor: IVisitor<T>): T {
|
||||
return visitor.visitNull()
|
||||
}
|
||||
|
||||
deserializeObject<T, V extends IVisitor<T>>(visitor: V): T {
|
||||
let next = this.buffer.take()
|
||||
if (next.next() === Token.LeftCurly) {
|
||||
|
||||
const value = visitor.visitObject(new CommaSeparated(this))
|
||||
|
||||
next = this.buffer.take()
|
||||
if (next.next() === Token.RightCurly) {
|
||||
return value
|
||||
} else {
|
||||
throw unexpected('}', next.toString(), this.buffer.position)
|
||||
}
|
||||
} else {
|
||||
throw unexpected('{', next.toString(), this.buffer.position)
|
||||
}
|
||||
deserializeObject<T>(visitor: IVisitor<T>): T {
|
||||
const value = this.parser(this.input) as object
|
||||
return visitor.visitObject(JSONMapAccess.fromObject(value))
|
||||
}
|
||||
|
||||
deserializeClass<T, V extends IVisitor<T>>(_name: string, visitor: V): T {
|
||||
return this.deserializeObject(visitor)
|
||||
deserializeIterable<T>(visitor: IVisitor<T>): T {
|
||||
const value = this.parser(this.input) as Iterable<any>
|
||||
return visitor.visitIterable(JSONIterableAccess.fromIterable(value))
|
||||
}
|
||||
|
||||
deserializeString<T, V extends IVisitor<T>>(visitor: V): T {
|
||||
const next = this.buffer.take()
|
||||
if (next.next() === Token.Quote) {
|
||||
let index = -1
|
||||
|
||||
do {
|
||||
index = this.buffer.indexOf(Token.Quote, index)
|
||||
} while (index > -1 && this.buffer.at(index - 1) === Token.ForwardSlash)
|
||||
|
||||
if (index === -1) {
|
||||
throw new SyntaxError('Unterminated string literal')
|
||||
}
|
||||
|
||||
const bytes = this.buffer.take(index)
|
||||
this.buffer.take()
|
||||
return visitor.visitString(bytes.toString())
|
||||
} else {
|
||||
throw unexpected('"', next.toString(), this.buffer.position)
|
||||
}
|
||||
deserializeFunction<T>(_visitor: IVisitor<T>): T {
|
||||
throw new Error('Method not implemented')
|
||||
}
|
||||
|
||||
deserializeNumber<T, V extends IVisitor<T>>(visitor: V): T {
|
||||
const next = this.buffer.peek().next()
|
||||
|
||||
if (isNumericToken(next)) {
|
||||
const digits = this.buffer.takeWhile(isNumericToken).toString()
|
||||
if (digits.length >= 16) {
|
||||
const number = BigInt(digits)
|
||||
return visitor.visitBigInt(number)
|
||||
} else if (digits.length > 0) {
|
||||
let number = parseInt(digits.toString(), 10)
|
||||
return visitor.visitNumber(number)
|
||||
}
|
||||
}
|
||||
|
||||
throw unexpected('"-", ".", or 0..=9', next.toString(), this.buffer.position)
|
||||
}
|
||||
|
||||
deserializeBigInt<T, V extends IVisitor<T>>(visitor: V): T {
|
||||
return this.deserializeNumber(visitor)
|
||||
}
|
||||
|
||||
deserializeBoolean<T, V extends IVisitor<T>>(visitor: V): T {
|
||||
const next = this.buffer.next()
|
||||
let length = 3
|
||||
|
||||
switch (next) {
|
||||
case Literal.False[0]:
|
||||
length = 4
|
||||
case Literal.True[0]:
|
||||
break
|
||||
default:
|
||||
throw unexpected('"true" or "false"', this.buffer.next().toString(), this.buffer.position)
|
||||
}
|
||||
|
||||
this.buffer.take(length)
|
||||
return visitor.visitBoolean(length === 3)
|
||||
}
|
||||
|
||||
deserializeSymbol<T, V extends IVisitor<T>>(_visitor: V): T {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
deserializeIterable<T, V extends IVisitor<T>>(visitor: V): T {
|
||||
let next = this.buffer.take()
|
||||
if (next.next() === Token.LeftSquare) {
|
||||
|
||||
const value = visitor.visitIterable(new CommaSeparated(this))
|
||||
|
||||
next = this.buffer.take()
|
||||
if (next.next() === Token.RightSquare) {
|
||||
return value
|
||||
} else {
|
||||
throw unexpected(']', next.toString(), this.buffer.position)
|
||||
}
|
||||
} else {
|
||||
throw unexpected('[', next.toString(), this.buffer.position)
|
||||
}
|
||||
}
|
||||
|
||||
deserializeFunction<T, V extends IVisitor<T>>(_visitor: V): T {
|
||||
throw new Error('Method not implemented.')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@ import { serialize } from 'serde/ser'
|
|||
|
||||
export function toString(value: any): string {
|
||||
const serializer = new JSONSerializer()
|
||||
serialize(serializer, value)
|
||||
return serializer.output
|
||||
return serialize(serializer, value)
|
||||
}
|
||||
|
||||
export function fromString<T>(value: string, into: any): T {
|
||||
|
|
137
src/ser.ts
137
src/ser.ts
|
@ -1,106 +1,79 @@
|
|||
import { ISerializeIterable, ISerializeObject, ISerializer, serialize, SerializeObject } from "serde/ser"
|
||||
import { getSerialize, GlobalRegistry, Registry } from 'serde'
|
||||
import { IdentitySerializer, ISerializer, serialize, SerializeIterable, SerializeObject, Serializer } from 'serde/ser'
|
||||
|
||||
class JSONObjectSerializer extends SerializeObject<void> {
|
||||
private ser: JSONSerializer
|
||||
private first: boolean = true
|
||||
interface Stringify {
|
||||
(value: any): string
|
||||
}
|
||||
|
||||
constructor(serializer: JSONSerializer) {
|
||||
type Replacer = (key: PropertyKey, value: any) => any
|
||||
|
||||
class JSONSerializeObject extends SerializeObject<string> {
|
||||
private readonly stringify: Stringify
|
||||
private value: Record<PropertyKey, any> = {}
|
||||
private currentKey?: string
|
||||
|
||||
constructor(stringify: Stringify) {
|
||||
super()
|
||||
this.ser = serializer
|
||||
serializer.write('{')
|
||||
this.stringify = stringify
|
||||
}
|
||||
|
||||
serializeKey<U>(key: U): void {
|
||||
if (!this.first) {
|
||||
this.ser.write(',')
|
||||
} else {
|
||||
this.first = false
|
||||
}
|
||||
|
||||
serialize(this.ser, key)
|
||||
this.ser.write(':')
|
||||
serializeKey(key: string): void {
|
||||
this.currentKey = key
|
||||
}
|
||||
|
||||
serializeValue<U>(value: U): void {
|
||||
serialize(this.ser, value)
|
||||
this.value[this.currentKey!] = value
|
||||
this.currentKey = undefined
|
||||
}
|
||||
|
||||
end(): void {
|
||||
this.ser.write('}')
|
||||
end(): string {
|
||||
return this.stringify(this.value)
|
||||
}
|
||||
}
|
||||
|
||||
class JSONIterableSerializer implements ISerializeIterable<void> {
|
||||
private ser: JSONSerializer
|
||||
private first: boolean = true
|
||||
class JSONSerializeIterable extends SerializeIterable<string> {
|
||||
private readonly stringify: Stringify
|
||||
private elements: any[] = []
|
||||
|
||||
constructor(serializer: JSONSerializer) {
|
||||
this.ser = serializer
|
||||
serializer.write('[')
|
||||
constructor(stringify: Stringify) {
|
||||
super()
|
||||
this.stringify = stringify
|
||||
}
|
||||
|
||||
serializeElement<U>(element: U): void {
|
||||
if (!this.first) {
|
||||
this.ser.write(',')
|
||||
} else {
|
||||
this.first = false
|
||||
}
|
||||
|
||||
serialize(this.ser, element)
|
||||
serializeElement<U>(value: U): void {
|
||||
this.elements.push(value)
|
||||
}
|
||||
|
||||
end(): void {
|
||||
this.ser.write(']')
|
||||
end(): string {
|
||||
return this.stringify(this.elements)
|
||||
}
|
||||
}
|
||||
|
||||
export class JSONSerializer implements ISerializer<void> {
|
||||
output: string = ''
|
||||
|
||||
write(value: string) {
|
||||
this.output += value
|
||||
}
|
||||
|
||||
serializeString(value: string) {
|
||||
this.write(`"${value}"`)
|
||||
}
|
||||
|
||||
serializeBoolean(value: boolean): void {
|
||||
this.write(value.toString())
|
||||
}
|
||||
|
||||
serializeSymbol(value: symbol): void {
|
||||
const key = Symbol.keyFor(value)
|
||||
if (key) {
|
||||
this.write(key)
|
||||
} else {
|
||||
return this.serializeString(value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
serializeObject(): ISerializeObject<void> {
|
||||
return new JSONObjectSerializer(this)
|
||||
}
|
||||
|
||||
serializeClass(_name: PropertyKey): ISerializeObject<void> {
|
||||
return this.serializeObject()
|
||||
}
|
||||
|
||||
serializeNumber(value: number) {
|
||||
this.write(value.toString())
|
||||
}
|
||||
|
||||
serializeBigInt(value: bigint) {
|
||||
this.write(value.toString())
|
||||
}
|
||||
|
||||
serializeIterable(): ISerializeIterable<void> {
|
||||
return new JSONIterableSerializer(this)
|
||||
}
|
||||
|
||||
serializeNull() {
|
||||
return this.write('null')
|
||||
}
|
||||
const id = <T, U>(_ser: ISerializer<T>, value: U) => value
|
||||
const serializer = (registry: Registry) => <T>(_key: PropertyKey, value: T) => {
|
||||
const ser = getSerialize(value, id, registry)
|
||||
return ser(new IdentitySerializer(), value)
|
||||
}
|
||||
|
||||
const stringify = (replacer: Replacer) => (value: any) => JSON.stringify(value, replacer)
|
||||
|
||||
export class JSONSerializer extends Serializer<string> {
|
||||
private stringify: (value: any) => string
|
||||
|
||||
constructor(registry: Registry = GlobalRegistry) {
|
||||
super()
|
||||
this.stringify = stringify(serializer(registry))
|
||||
}
|
||||
|
||||
serializeAny(value: any) { return this.stringify(value) }
|
||||
serializeBoolean(value: any) { return JSON.stringify(value) }
|
||||
serializeNumber(value: any) { return JSON.stringify(value) }
|
||||
serializeBigInt(value: any) { return JSON.stringify(value) }
|
||||
serializeString(value: any) { return JSON.stringify(value) }
|
||||
serializeSymbol(value: any) { return JSON.stringify(value) }
|
||||
serializeNull() { return JSON.stringify(null) }
|
||||
serializeIterable(_len: number) { return new JSONSerializeIterable(this.stringify) }
|
||||
serializeObject(_len: number) { return new JSONSerializeObject(this.stringify) }
|
||||
serializeClass(_name: string, _len: number) { return new JSONSerializeObject(this.stringify) }
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue