Open13

いろんな値をJSON化したり戻したりする

hashrockhashrock

何が問題なのか。

例えばこれはエラーになる。

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)
hashrockhashrock

というわけで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));

これで出力できるようになる。

hashrockhashrock

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
hashrockhashrock

また、文字列化ができたとして、編集をどう扱うかという問題もある。
結局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'],
  }
};
hashrockhashrock

実際に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型が選択されているとき、内部のオブジェクトは型が保存されないようだ。

hashrockhashrock

これは難しい問題だ。JSON型をストアすることを選択しているのだから、JSON標準に含まれない型は文字列に変換されても文句は言えないと思う。Value型は単体であれば下記のように選択することができる。

そもそもテキストエリアでJSON内の型をうまく扱える方法はない。話によると循環参照も作れてしまうらしく(v8 serializerができることは何でもできるということだろう)、完全にキリのない問題だ。

だったら無視して型は保存されませんでいいじゃないか、という話もあるのだけど、問題なのは createdAt はDate型を保持できたほうがいいだろうみたいな、些細な割に結構メジャーなケースがあり得ることだ。

hashrockhashrock

思いつく対応:

  1. superjsonのmeta情報をそのまま使う。型が変更されるような変更は許容しないか、ignore preserve typesのようなオプションをつける(つけた場合は無慈悲にすべてがstring型に変換される)
  2. JSON内にstringやnumber以外が含まれる場合には単純に編集をさせない
  3. テキストエリアで編集をさせず、専用のエディタを作成する(これはツリーエディタを作成することを意味する。労力の割にユーザの利便性が上がらない…)

他に、v8_valueserializerのデシリアライズ形式の一つに eval'able JavaScript representation というのがあって、それをそのまま使う手もある。これはv8_valueserializerでserializeができるようになったら選べる選択肢だ。
(今のところKV Editorが最初にもらえるのはJavascript Objectなので、一段レイヤが下のv8 serialize形式を使うにはもう一度serializeする必要がある)

hashrockhashrock

(1)の問題点の一つは、エディタからの新規作成のときに型を付与したくならないのか、という点だ。evalでオブジェクトを作って挿入してしまう手もあるが、そんなことわざわざやる人いるかな…

究極には(3)をやらなくてはいけない気がする。その場合バックエンドはv8 serializer形式で統一してしまって、フロントで直接いじるくらいの気合で作らなければいけない気がする。