# 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](https://serde.rs/) 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 five interfaces which are relevant to `serde-ts` users. ## `Serializer` ```ts interface ISerializer { 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 serializeIterable(len?: number): ISerializeIterable serializeObject(len?: number): ISerializeObject serializeClass(name: string, len?: number): ISerializeObject } ``` 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` function for each of `serde-ts`'s data types. ## `Deserializer` ```ts interface IDeserializer { deserializeAny(visitor: Partial>): T deserializeBoolean(visitor: Partial>): T deserializeNumber(visitor: Partial>): T deserializeBigInt(visitor: Partial>): T deserializeString(visitor: Partial>): T deserializeSymbol(visitor: Partial>): T deserializeNull(visitor: Partial>): T deserializeObject(visitor: Partial>): T deserializeIterable(visitor: Partial>): T deserializeFunction(visitor: Partial>): T } ``` a `Deserializer` is responsible for transforming the target serialization format into the internal `serde-ts` data model. it will have a `deserialize` for each of the `serde-ts`'s data types. ## `Serialize` ```ts interface Serialize { (serializer: ISerializer, 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` ```ts interface Deserialize { (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`. ## `Visitor` ```ts interface IVisitor { visitBoolean(value: boolean): T visitNumber(value: number): T visitBigInt(value: bigint): T visitString(value: string): T visitSymbol(value: symbol): T visitNull(): T visitObject(access: IMapAccess): T visitIterable(access: IIterableAccess): T } ``` a `Visitor` is part of the deserialization process and is how each data type is mapped from the internal data model to whatever the end structure is. `Deserialize` is responsible for creating a `Visitor` and giving it to a `Deserializer`. that `Visitor` will then be given every value once it has been deserialized into `serde-ts`'s internal model. # 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 ```ts import { registerSerialize, registerDeserialize } from 'serde' import { ISerializer } from 'serde/ser' import { forward, IDeserializer, IIterableAccess, IMapAccess } 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 } static serialize(serializer: ISerializer, value: Vector2) { const iter = serializer.serializeIterable() // returns an ISerializeIterable iter.serializeElement(value.x) iter.serializeElement(value.y) return iter.end() } static deserialize(deserializer: IDeserializer) { return 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]) } }) } } class Entity { name: string position: Vector2 constructor(name: string, position: Vector2) { this.name = name this.position = position } static serialize(serializer: ISerializer, value: Entity) { const ser = serializer.serializeObject() ser.serializeEntry('name', value.name) ser.serializeEntry('position', value.position) return ser.end() } static deserialize(deserializer: IDeserializer) { return deserializer.deserializeObject({ visitObject(access: IMapAccess) { let name, position for (const [key, value] of access) { switch (key) { case 'name': name = value break case 'position': // forward the deserialization to Vector2 position = forward(value as string, Vector2) break } } return new Entity(name as string, position as Vector2) } }) } } // we're registering to the global serde registry registerSerialize(Vector2, Vector2.serialize) registerDeserialize(Vector2, Vector2.deserialize) registerSerialize(Entity, Entity.serialize) registerDeserialize(Entity, Entity.deserialize) 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 } const player = new Entity('Player', one) const serializedPlayer = toString(player) console.log(serializedPlayer) // {"name":"Player","position":[1,1]} const deserializedPlayer = fromString(serializedPlayer, Entity) console.log(deserializedPlayer) // Entity { name: 'Player', position: Vector2 { x: 1, y: 1 } } ```