serde-ts/README.md

150 lines
5 KiB
Markdown

# 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<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>
serializeIterable(): ISerializeIterable<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`
```ts
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>(https://git.kitsu.cafe/rowan/serde-tsvisitor: 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`
```ts
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`
```ts
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`.
## `Visitor`
```ts
interface IVisitor<T> {
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 { 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 }
```