@unikvs/json
値を JSON や JSON Lines (JSONL) でシリアライズする Transformer プラグインの使い方を説明します。
概要
@unikvs/json は、値を標準の JSON.stringify と JSON.parse でシリアライズする Transformer プラグインです。Transformer 一括変換では 1 つの値を 1 つの JSON 文字列として扱い、ストリームでは JSON Lines (JSONL / NDJSON) として 1 行につき 1 つの値を扱います。
追加の依存はなく、実行環境の TextEncoder・TextDecoder と JSON API だけで動作します。型情報を持たない素の JSON のため、Date や BigInt、Map・Set のような値は種類を保ったまま往復できません。
常にオープン状態です。open・close は不要で、isOpen は常に true、name は常に "Json" を返します。
インストール
インストールは次のとおりです。
npm install @unikvs/jsonpnpm add @unikvs/jsonyarn add @unikvs/jsonbun add @unikvs/jsonnub add @unikvs/jsonaube add @unikvs/json対応する値
JSON で表現できる値はそのまま往復できます。型ごとの扱いは次のとおりです。
| 型 | 例 | 扱い |
|---|---|---|
null |
null |
対応します。 |
| 真偽値 | true・false |
対応します。 |
| 数値 | 42・-0.5 |
対応します。NaN・Infinity・-Infinity は null、-0 は 0 になります。 |
| 文字列 | "hello"・"😀" |
対応します。UTF-8 でエンコードします。 |
| 配列 | [1, "a", null] |
対応します。 |
| オブジェクト | { "a": 1 } |
対応します。 |
undefined |
undefined |
ルートでは JsonUnsupportedValueError を投げます。入れ子ではプロパティーごと省略されます。 |
| 関数 | () => {} |
ルートでは JsonUnsupportedValueError を投げます。入れ子ではプロパティーごと省略されます。 |
| シンボル | Symbol() |
ルートでは JsonUnsupportedValueError を投げます。入れ子ではプロパティーごと省略されます。 |
BigInt |
1n |
ネイティブの TypeError を投げます。 |
Date |
new Date() |
toJSON により ISO 8601 文字列になります。 |
Map・Set |
new Map() |
空のオブジェクトになります。 |
使い方
Json のコンストラクターは引数を取りません。
import { Json } from "@unikvs/json";
const json = new Json();
主なメンバーは次のとおりです。
| メンバー | 概要 |
|---|---|
new Json() |
引数なしで初期化します。 |
name |
常に "Json" を返します。 |
isOpen |
常に true を返します。 |
encode({ data }) |
値を JSON 文字列のバイト列へ変換します。 |
decode({ data }) |
JSON 文字列のバイト列を値へ変換します。 |
getEncodable() |
値を JSONL のバイト列へ変換する TransformStream を返します。 |
getDecodable() |
JSONL のバイト列を値へ変換する TransformStream を返します。 |
UniKvs には appendTransformer で登録します。以降に登録するストレージの前段になります。
import { Json } from "@unikvs/json";
import UniKvs from "unikvs";
const kvs = UniKvs.config()
.appendTransformer(new Json())
.appendStorage(storage)
.create();
await kvs.open();
await kvs.set("profile", { name: "tai-kun", tags: ["json"] });
const profile = await kvs.get("profile");
set でシリアライズして保存し、get で自動的にデシリアライズします。
ストリーム
getEncodable・getDecodable は JSON Lines (JSONL / NDJSON) を扱います。JSONL は 1 行につき 1 つの JSON 値を並べた形式です。
エンコードの動きは次のとおりです。
- 入力チャンク 1 つを 1 つの値として
JSON.stringifyし、末尾に\nを付けて UTF-8 で出力します。 - 文字列中の改行や
\rはJSON.stringifyがエスケープするため、行の区切りと衝突しません。
デコードの動きは次のとおりです。
\nで区切って 1 行ずつJSON.parseします。行の区切りが任意のバイト境界で分割されてもかまいません。マルチバイト文字が途中で分割されても正しく復元されます。- 行末の
\rを 1 つだけ取り除くため、CRLF の改行も受理します。 - 完全に空の行は読み飛ばします。
- 最終行は末尾の改行がなくてもデコードします。空のストリームは値を 1 つも出力せず、エラーにもなりません。
- JSON として不正な行があると、ネイティブの
SyntaxErrorでストリームが失敗します。
import { Json } from "@unikvs/json";
const json = new Json();
const source = new ReadableStream({
start(controller) {
controller.enqueue({ id: 1 });
controller.enqueue({ id: 2 });
controller.close();
},
});
const decoded = source.pipeThrough(json.getEncodable()).pipeThrough(json.getDecodable());
一括変換の encode・decode は JSON 値 1 つだけを扱います。複数の値を連結して decode へ渡すと、JSON の後に続く内容として SyntaxError になります。複数の値をまとめて扱う場合はストリームを使ってください。
エラー
このパッケージが投げるエラーは次のとおりです。
| エラー | 意味 | 対処 |
|---|---|---|
JsonUnsupportedValueError |
ルートの値が undefined・関数・シンボルで、JSON にシリアライズできません。meta.type に typeof の結果を保持します。 |
null に置き換えるか、JSON で表現できる値へ変換してください。 |
BigInt には JSON.stringify がネイティブの TypeError を投げます。decode の失敗もネイティブのエラーとして伝わります。
- JSON として不正、空のバイト列、JSON 値の後に続く内容は
SyntaxErrorです。 - UTF-8 として不正なバイト列は
TypeErrorです。 - ストリームのエンコードで未対応の値を流すと
JsonUnsupportedValueError、デコードで不正な行を流すとSyntaxErrorでストリームが失敗します。
import { JsonUnsupportedValueError } from "@unikvs/json";
try {
json.encode({ data: undefined });
} catch (error) {
if (error instanceof JsonUnsupportedValueError) {
console.log(error.meta.type);
} else {
throw error;
}
}
使用例
Memory と組み合わせた最小例です。書き込んだ値は JSON 文字列として保存し、読み取り時に自動でデシリアライズします。
import { Json } from "@unikvs/json";
import { Memory } from "@unikvs/memory";
import UniKvs, { type PlainValue } from "unikvs";
type Profile = {
name: string;
tags: string[];
};
const kvs = UniKvs.config<{ profile: PlainValue<Profile> }>()
.appendTransformer(new Json())
.appendStorage(new Memory())
.create();
await kvs.open();
await kvs.set("profile", { name: "tai-kun", tags: ["json", "transformer"] });
const profile = await kvs.get("profile");
console.log(profile.name);
await kvs.close();