🎶

ts-serde - TypeScriptにおけるserdeライブラリ

2025/01/14に公開

導入

Rustにはシリアライズ・デシリアライズのインターフェースライブラリ(フレームワーク)としてserdeというものがあります。
私はTypeScriptをよく使うのでTypeScript版のserdeが欲しくなったので作りました。
とはいえ、Rustのような大規模なフレームワークではなく、シリアライズ・デシリアライズを統一した規格で行うためのライブラリセットです。

ts-serde

https://github.com/jill64/ts-serde

いつも通り依存関係をインストールします。

npm i ts-serde

型定義

ts-serdeからは3つの型定義がエクスポートされています。

import { Serde } from 'ts-serde'
import { Serialize, Deserialize } from 'ts-serde/types'

それぞれの中身は以下のようになっています。

type Serialize<T> = (val: T) => string

type Deserialize<T> = (str: string) => T

type Serde<T> = {
  serialize: Serialize<T>
  deserialize: Deserialize<T>
}

単純ですね。あとはこれを規格化して、serdeの複雑度を軽減していきます。

プリミティブ型

import { string, number, boolean, bigint, integer } from 'ts-serde/primitive'

5つのserdeが準備されています。
それぞれ、JavaScriptのプリミティブ型と文字列型の変換に対応しています。

string-serde.ts
import type { Serde } from 'ts-serde'

export const string: Serde<string> = {
  serialize: String,
  deserialize: String
}
number-serde.ts
import type { Serde } from 'ts-serde'

export const number: Serde<number> = {
  serialize: String,
  deserialize: Number
}
bigint-serde.ts
import type { Serde } from 'ts-serde'

export const bigint: Serde<bigint> = {
  serialize: String,
  deserialize: BigInt
}
boolean-serde.ts
import type { Serde } from 'ts-serde'

export const boolean: Serde<boolean> = {
  serialize: String,
  deserialize: (str) => str === 'true'
}
integer-serde.ts
import type { Serde } from 'ts-serde'

export const integer: Serde<number> = {
  serialize: String,
  deserialize: (str) => {
    const n = parseInt(str)
    return isNaN(n) ? 0 : n
  }
}

列挙型(Enums)

特定の値だけを変換したいユースケースもあるでしょう。
そんな時は列挙型(Enums)が使えます。

enums-serde.ts
import { enums } from 'ts-serde/object'

// 列挙したい値を配列で定義する
const e = enums(['foo', 'bar', 'baz'])

// 'foo'を表示
console.log(e.serialize('foo'))

// 'bar'を表示
console.log(e.deserialize('bar'))

// 一覧にないのでエラーをスロー
e.deserialize('qux')

また、第二引数でフォールバック値を指定することで、リスト外の値が入力された際に、その値を代わりに使用します。

import { enums } from 'ts-serde/object'

const e = enums(['foo', 'bar', 'baz'], 'fallback')

// 'fallback'を表示
console.log(e.deserialize('qux'))

オブジェクト型

オブジェクト型の変換にはJSONdevalueの2つのメソッドが用意されています。

json-serde.ts
import { json } from 'ts-serde/object'

const j = json(
  (x): x is { key: string } => {
    return typeof x === 'object' && !!x && ('key' in x) && typoef x.key === 'string'
  }
)

// '{"key":"value"}'を表示
console.log(j.serialize({ key: 'value' }))

// obj = { key: 'value2' }
const obj = j.deserialize('{"key":"value2"}')

// 型が違うのでエラー
j.deserialize('{"key2":"value2"}')

ここで型ガードが面倒だと感じる方はこのライブラリをお勧めします

https://github.com/yona3/typescanner

このライブラリを使うことで、型ガードをこのように簡単に記述できます。

json-serde.ts
import { json } from 'ts-serde/object'
import { string } from 'typescanner'

const isValid = scanner({
    key: string
})

const j = json(isValid)

enumsと同じく、第二引数でフォールバック値も指定できます。

import { json } from 'ts-serde/object'
import { string } from 'typescanner'

const isValid = scanner({
    key: string
})

const j = json(isValid, { key: 'fallback' })

// objには{ key: 'fallback' }が格納される
const obj = j.deserialize('{"key2":"value2"}')

devalue

devalueは同名のライブラリdevalueを使用してシリアライズ・デシリアライズします。これにより、通常のJSONではサポートされないMap型やSet型、Date型なども扱うことができます。基本的な使い方はJSONと同じです。

devalue-serde.ts
import { devalue } from 'ts-serde/object'

const d = devalue(
  (x): x is Set<Date> =>
    // ... Type Guard
    ,
    null // fallback value
)

d.serialize(new Set([new Date()]))
// => '[["Set",1],["Date","20XX-01-01T00:00:00.000Z"]]'

d.deserialize('') // => null (fallback value)

まとめ

いかがだったでしょうか。
この記事がアプリケーション内の統一したシリアライズ・デシリアライズ機能の助けになれば幸いです。
またts-serdeについてバグや不明点がありましたらぜひIssueを開いてください。

Discussion