いろんな値をJSON化したり戻したりする
これの続き。Deno KVのVSCode拡張の実装を改善したい。
実験する場所。
何が問題なのか。
例えばこれはエラーになる。
JSON.stringify(BigInt(1))
error: Uncaught TypeError: Do not know how to serialize a BigInt
JSON.stringify(BigInt(1))
^
at JSON.stringify (<anonymous>)
同じ理由でKvU64もだめ
class KvU64 {
constructor(value: bigint){
this.value = value;
}
readonly value: bigint;
}
const key: KvU64 = new KvU64(1n);
JSON.stringify(key)
JSON.stringifyのreplacerの使い方。
というわけでreplacerを使う。
const key = 1n;
function replacer(_key: string, value: unknown) {
if (typeof value === "bigint") {
return value.toString();
}
return value;
}
console.log(JSON.stringify(key, replacer));
これで出力できるようになる。
しかし、MapやSetなどのobjectはどうするか。標準だと {}
しか出力されない。
replacerを使ってそれをやった人の記事。
Deno KVに関しては key, valueごとに対応している型が異なる。
(keyは独自フォーマット、valueはv8 serializer)
こいつらを個別にreplacerを書くのかというと結構面倒。
key:
- string
- number
- boolean
- Uint8Array
- bigint
value:
- undefined
- null
- boolean
- number
- string
- bigint
- Uint8Array
- Array
- Object
- Map
- Set
- Date
- RegExp
また、文字列化ができたとして、編集をどう扱うかという問題もある。
結局JSON文字列に直すことで型の情報が消えてしまい、値を上書きするときに問題が起きてしまう。
わかりやすく言えばDate型のvalueが toStringされたものに戻ってしまい、オリジナルのオブジェクトがDate型なのかString型なのかを判定することができなくなる。
superjsonではこれをmeta情報としてvalueとは分離した形で持つようにしている。KV editor拡張ではクラサバのやり取りはこれで行っている。
json = {
normal: 'string',
timestamp: "2020-06-20T04:56:50.293Z",
test: "/superjson/",
};
// note that `normal` is not included here; `meta` only has special cases
meta = {
values: {
timestamp: ['Date'],
test: ['regexp'],
}
};
同じ問題を扱っている kview の実装を見ていく。
Valueを変換しているところは下記。基本的に{type, value}
の形に変換している。
実際にeditするとどうなるか。
まず値をsetしてみる。
const kv = await Deno.openKv();
const item = {
id: 1,
name: "John Doe",
createdAt: new Date(),
tags: new Set(["a", "b", "c"]),
};
await kv.set(["type", 1], item);
その後値をedit.
const kv = await Deno.openKv();
const a = await kv.get(["type", 1]) as { value: { createdAt: any } };
console.log(typeof a.value.createdAt);
string
JSON型が選択されているとき、内部のオブジェクトは型が保存されないようだ。
これは難しい問題だ。JSON型をストアすることを選択しているのだから、JSON標準に含まれない型は文字列に変換されても文句は言えないと思う。Value型は単体であれば下記のように選択することができる。
そもそもテキストエリアでJSON内の型をうまく扱える方法はない。話によると循環参照も作れてしまうらしく(v8 serializerができることは何でもできるということだろう)、完全にキリのない問題だ。
だったら無視して型は保存されませんでいいじゃないか、という話もあるのだけど、問題なのは createdAt
はDate型を保持できたほうがいいだろうみたいな、些細な割に結構メジャーなケースがあり得ることだ。
思いつく対応:
- superjsonのmeta情報をそのまま使う。型が変更されるような変更は許容しないか、ignore preserve typesのようなオプションをつける(つけた場合は無慈悲にすべてがstring型に変換される)
- JSON内にstringやnumber以外が含まれる場合には単純に編集をさせない
- テキストエリアで編集をさせず、専用のエディタを作成する(これはツリーエディタを作成することを意味する。労力の割にユーザの利便性が上がらない…)
他に、v8_valueserializerのデシリアライズ形式の一つに eval'able JavaScript representation
というのがあって、それをそのまま使う手もある。これはv8_valueserializerでserializeができるようになったら選べる選択肢だ。
(今のところKV Editorが最初にもらえるのはJavascript Objectなので、一段レイヤが下のv8 serialize形式を使うにはもう一度serializeする必要がある)
(1)の問題点の一つは、エディタからの新規作成のときに型を付与したくならないのか、という点だ。evalでオブジェクトを作って挿入してしまう手もあるが、そんなことわざわざやる人いるかな…
究極には(3)をやらなくてはいけない気がする。その場合バックエンドはv8 serializer形式で統一してしまって、フロントで直接いじるくらいの気合で作らなければいけない気がする。
この辺を使えたらいいかもしれない(storybookの中で使われていたJSONツリーエディタ)
ただし、これはbigintなどに対応していない(はず)