serde-ts/README.md

4.2 KiB

serde-ts

a library for serializing and deserializing javascript objects

Usage

this library makes no assumptions about formats and loosely follows the architecture of Rust's serde crate. the major difference are the particular data types. serde-ts's internal data model contains the following types

serde-ts data types

  • null/undefined
  • boolean
  • number
  • bigint
  • string
  • symbol
  • function
  • object
  • iterable
  • class

with the exception of iterables and classes, these types were chosen as they are the ones provided by Javascript's native typeof operator which is used internally for type checking.

there are four interfaces which are relevant to serde-ts users.

Serializer

interface ISerializer<T> {
  serializeAny?(value: any): T
  serializeBoolean(value: boolean): T
  serializeNumber(value: number): T
  serializeBigInt(value: bigint): T
  serializeString(value: string): T
  serializeSymbol(value: symbol): T
  serializeNull(): T
  serializeObject(): ISerializeObject<T>
  serializeClass(name: string): ISerializeObject<T>
}

a Serializer is responsible for transforming the internal serde-ts data model into the target serialization format. this means that it will have a serialize<Type> function for each of serde-ts's data types.

Deserializer

interface IDeserializer {
  deserializeAny<T>(visitor: Partial<IVisitor<T>>): T
  deserializeBoolean<T>(visitor: Partial<IVisitor<T>>): T
  deserializeNumber<T>(visitor: Partial<IVisitor<T>>): T
  deserializeBigInt<T>(visitor: Partial<IVisitor<T>>): T
  deserializeString<T>(visitor: Partial<IVisitor<T>>): T
  deserializeSymbol<T>(visitor: Partial<IVisitor<T>>): T
  deserializeNull<T>(visitor: Partial<IVisitor<T>>): T
  deserializeObject<T>(visitor: Partial<IVisitor<T>>): T
  deserializeIterable<T>(visitor: Partial<IVisitor<T>>): T
  deserializeFunction<T>(visitor: Partial<IVisitor<T>>): T
}

a Deserializer is responsible for transforming the target serialization format into the internal serde-ts data model. it will have a deserialize<Type> for each of the serde-ts's data types.

Serialize

interface Serialize<T> {
  <U>(serializer: ISerializer<T>, value: U): T
}

Serialize is responsible for transforming a value to the internal data model. for example, a Vector2 may be internally serialized as an iterable of numbers.

Deserialize

interface Deserialize<T> {
  (deserializer: IDeserializer): T
}

Deserialize is responsible for transforming a value from the internal data model. it is the inverse of Serialize. depending on the Deserialize target, it may accept an iterable of numbers and produce a Vector2.

Example

in simple cases, a formatter for serde-ts will expose a pair of functions like toString and fromString which will handle serializing and deserializing for you.

in more complex cases, you still won't be implementing Serializer/Deserializer, only consuming them with Serialize/Deserialize. assuming we're using a format which reads/writes JSON

import { registerSerialize, registerDeserialize } from 'serde'
import { ISerializer } from 'serde/ser'
import { IDeserializer, IIterableAccess } from 'serde/de'
import { fromString, toString } from 'serde-json-ts'

class Vector2 {
  x: number
  y: number

  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }
}

// we're registering to the global serde registry
registerSerialize(Vector2, (serializer: ISerializer<void>, value: Vector2) => {
  const iter = serializer.serializeIterable() // returns an ISerializeIterable<void>
  iter.serializeElement(value.x)
  iter.serializeElement(value.y)
  return iter.end()
})

registerDeserialize(Vector2, (deserializer: IDeserializer) => deserializer.deserializeIterable({
  // we could implement visitNumber here, but we'll let the default
  // deserializer handle it
  visitIterable(access: IIterableAccess) {
    const elements = []

    for (const item of access) {
      elements.push(item as number)
    }

    return new Vector2(elements[0], elements[1])
  }
})
)

const one = new Vector2(1, 1)
const serialized = toString(one)
console.log(serialized)
// "[1, 1]"

const deserializedOne = fromString(serialized, Vector2)
console.log(deserializedOne)
// Vector2 { x: 1, y: 1 }