clickhouse-js-node-coding/reference/custom-json.md
Version 356a8c1b9a73.bb1 · Apache-2.0. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.
← Return to resource and package checksum
Custom JSON parse / stringify
Requires: client
>= 1.14.0(configurablejson.parseandjson.stringify). Earlier versions cannot swap the JSON implementation.
Answer checklist
When the user wants UInt64/Int64 values back as BigInt:
- State that configurable
json.parse/json.stringifyrequires@clickhouse/client >= 1.14.0. - Show the supported
createClient({ json: { parse, stringify } })option, usually withjson-bigintanduseNativeBigInt: true. - Combine it with
output_format_json_quote_64bit_integers: 0so the server emits unquoted 64-bit integers that the parser can turn intoBigInt. - Mention that
output_format_json_quote_64bit_integers: 0is the default since ClickHouse25.8, but setting it explicitly is useful for older servers or portable examples. - Warn that casting to JavaScript
Number/parseInt/parseFloatloses precision aboveNumber.MAX_SAFE_INTEGER.
Why customize?
The default JSON.stringify / JSON.parse:
- Throws on
BigInt. - Calls
Date.prototype.toJSON()(ISO string) — fine forDateTimewithdate_time_input_format: 'best_effort', surprising in some workflows. - Loses precision for 64-bit integers returned as numbers (a separate issue — covered in the troubleshooting skill).
A custom { parse, stringify } lets you plug in JSONBig,
safe-stable-stringify, your own BigInt-aware serializer, etc.
Recipe: BigInt-safe stringify, custom Date handling
import { createClient } from "@clickhouse/client";
const valueSerializer = (value: unknown): unknown => {
// Serialize Date as a UNIX millis number (instead of toJSON's ISO string)
if (value instanceof Date) {
return value.getTime();
}
// Serialize BigInt as a string so JSON.stringify won't throw
if (typeof value === "bigint") {
return value.toString();
}
if (Array.isArray(value)) {
return value.map(valueSerializer);
}
if (typeof value === "object" && value !== null) {
return Object.fromEntries(
Object.entries(value).map(([k, v]) => [k, valueSerializer(v)]),
);
}
return value;
};
const client = createClient({
json: {
parse: JSON.parse, // use default parsing
stringify: (obj: unknown) => JSON.stringify(valueSerializer(obj)),
},
});
await client.command({
query: `
CREATE OR REPLACE TABLE inserts_custom_json_handling
(id UInt64, dt DateTime64(3, 'UTC'))
ENGINE MergeTree
ORDER BY id
`,
});
await client.insert({
table: "inserts_custom_json_handling",
format: "JSONEachRow",
values: [
{
id: BigInt("250000000000000200"), // serialized as a string
dt: new Date(), // serialized as ms since epoch
},
],
});
await client.close();
The custom
valueSerializerruns beforeJSON.stringify, so values are transformed before the standard hooks (Date.prototype.toJSON, objecttoJSON()methods, etc.) ever run.
Recipe: BigInt-safe parsing for 64-bit integer columns
If you want UInt64/Int64 to come back as BigInts (instead of strings
or precision-lossy numbers), plug in a BigInt-aware parser such as
json-bigint:
import { createClient } from "@clickhouse/client";
import JSONBig from "json-bigint";
const bigJson = JSONBig({ useNativeBigInt: true });
const client = createClient({
json: {
parse: bigJson.parse,
stringify: bigJson.stringify,
},
clickhouse_settings: {
output_format_json_quote_64bit_integers: 0,
},
});
output_format_json_quote_64bit_integers: 0 is the default since
ClickHouse 25.8; setting it explicitly is useful for older servers and
makes the example self-contained. With it off, the server emits unquoted
64-bit integers that json-bigint parses straight to BigInt. The
json option applies to both outgoing JSON bodies and incoming
JSON-format responses.
Recipe: Zero-dep BigInt parsing (no npm install)
If adding a dependency is awkward (locked lockfile, restricted environment,
or you just don't want to pull in json-bigint), you can plug in a
hand-rolled reviver. This uses the context.source argument that
JSON.parse revivers expose on capable runtimes. Feature-check support on
your deployed Node version; source text can preserve an exact integer after
ordinary JSON parsing has rounded the number:
import { createClient } from "@clickhouse/client";
const parseBigInt = (text: string) =>
JSON.parse(text, function (key, value, context) {
if (key.endsWith("__bigint")) {
if (typeof context?.source !== "string" || !/^-?[0-9]+$/.test(context.source)) {
throw new TypeError("Exact integer source required for a __bigint field");
}
return BigInt(context.source);
}
return value;
});
const client = createClient({
json: {
parse: parseBigInt,
stringify: JSON.stringify, // use default stringify
},
clickhouse_settings: { output_format_json_quote_64bit_integers: 0 },
});
const rs = await client.query({
query: "SELECT toUInt64(250000000000000200) AS id__bigint",
});
const { data } = await rs.json();
console.log(data[0].id__bigint); // 250000000000000200
await client.close();
Trade-offs versus json-bigint:
- ✓ No new dependency to install.
- ✓ Only promotes known to be 64-bit integers to
BigInt. - ✗ Requires actual reviver
context.sourcesupport. Test that capability; on an unsupported runtime, use quoted integers or a suitable exact parser. - ✗ Outgoing
stringifystill uses defaultJSON.stringify, which throws onBigInt. Pair with thevalueSerializerpattern above if your inserts containBigIntvalues.
Common pitfalls
- Setting
json.parseonly. That only affects reading JSON responses; outgoing JSON bodies usejson.stringify. If you want consistent custom handling in both directions, generally provide a matchingstringifytoo or a throwing serializer that prevents mismatches. - Forgetting
biginthandling instringify. DefaultJSON.stringifythrows onBigInt; if your data ever contains one, the insert will fail withTypeError: Do not know how to serialize a BigInt. - Targeting client
< 1.14.0. Thejsonoption doesn't exist; you'll need to convert values manually before callinginsert()/query()(or upgrade). - Casting 64-bit integers to
Number. JavaScript'snumbertype has only 53 bits of mantissa — values aboveNumber.MAX_SAFE_INTEGER(2^53 − 1) are silently rounded. Do not try to fix precision loss by callingNumber(),parseInt(), orparseFloat()on the value. The correct fix is aBigInt-aware parser (shown above), not a lossy cast. - Mixing BigInt and number for the same column. If some values are
BigIntand others arenumber, your app code needs to handle both types. Otherwise JavaScript will throw aTypeError: Cannot mix BigInt and other types.