134 lines
4.2 KiB
Markdown
134 lines
4.2 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 four 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>
|
|
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>(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`
|
|
```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`.
|
|
|
|
|
|
# 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 }
|
|
```
|
|
|