◆ 単純に文字列化のみで良い値なら JavaScript で実装した方が速い
◆ 文字列のエスケープ処理は JSON.stringify の方が速い
◆ オブジェクトのプロパティ列挙も JSON.stringify の方が速い
◆ 基本は JSON.stringify で十分
◆ 更に速度が必要ならスキーマ定義して Fastify で使われてるライブラリなどを使う

Fastify が生の Node.js より速い理由を調べたときは JSON.stringify が遅いのが原因でした
ただの文字列結合と比べたら 40 倍近く差がありました

これはもしかするとインデントや置換のオプションなしが前提なら普通に JavaScript で実装したほうが速い可能性もあったりするのでしょうか?
というわけで JSON.stringify を自作しました
オプションはなしで 事前のスキーマ定義も使わないものです

const esc = {
"\\": "\\\\",
'"': '\\"',
"\t": "\\t",
"\n": "\\n",
"\r": "\\r",
"\f": "\\f",
"\b": "\\b",
}

const esc_re = /[\\"\t\n\r\f\b]/g

const jsonstr = (str) => {
return '"' + str.replace(esc_re, x => esc[x]) + '"'
}

const json = (value) => {
const type = typeof value
if (type === "undefined" || type === "symbol" || type === "function") return
if (type === "boolean" || value === null) return String(value)
if (type === "number") return String(isFinite(value) ? value : null)
if (type === "string") return jsonstr(value)
if (value.toJSON) return json(value.toJSON())

if (Array.isArray(value)) {
let values = ""
for (const item of value) {
const v = json(item)
if (v !== undefined) values += "," + v
}
return "[" + values.slice(1) + "]"
}

if (typeof value === "object") {
let values = ""
for (const [key, item] of Object.entries(value)) {
const v = json(item)
if (v !== undefined) values += "," + jsonstr(key) + ":" + v
}
return "{" + values.slice(1) + "}"
}

throw new Error("unexpected")
}

100% 互換のつもりはないですが 一般的に使いそうな値は同じになるようしたつもりです

const test = (x) => {
const a = JSON.stringify(x)
const b = json(x)
if (a !== b) {
console.error(a, b)
throw new Error("not equal")
}
}

const values = [
1, null, false, undefined, [], {}, new Date(), new RegExp(),
[1, 2], {a: "a"}, -0.1, NaN, Infinity, "", "\\", "\t\n\r", "あ",
{x: 1, y: null, z: ["a", "b", 3, {s:1,t:undefined,u:Symbol(), p(){}, q: [[[0]]]}], g: new Date("")}
]

for (const value of values) {
test(value)
}
// no error

数値・文字列・真偽値・ null ・配列・オブジェクトはもちろん 出力されない undefined や Symbol や関数も試してます
数値は Infinity や NaN の扱い 文字列は \\ や \n のエスケープも対応しています
Date 型のような toJSON メソッドがあるオブジェクトはその結果を使います

この JSON 化関数と速度を比べてみます

const compare = (x) => {
if (JSON.stringify(x) === json(x)) {
const n1 = performance.now()
for(let i=0;i<100_000;i++) JSON.stringify(x)
const n2 = performance.now()
for(let i=0;i<100_000;i++) json(x)
const n3 = performance.now()
return [n2 - n1, n3 - n2]
}
}

for(const value of values) {
console.log(value, compare(value))
console.log(value, compare(value))
console.log(value, compare(value))
}

10 万回実行してかかった時間(ms)をそれぞれ 3 回表示します

valueJSON.stringify()json()
116.2550000241026313.109999941661954
116.880000010132793.2549999887123704
112.1100000105798242.840000088326633
null10.9299999894574282.6549999602138996
null10.7350000180304052.8300000121816993
null10.5099999345839022.6650000363588333
false10.8499999623745682.624999964609742
false11.4199999952688812.744999947026372
false10.779999895021322.585000009275973
undefined8.2149999216198920.7800000021234155
undefined7.86999997217208150.7399999303743243
undefined7.9350000014528630.7599999662488699
[]22.9450000915676363.7799999117851257
[]23.2349999714642763.8400000194087625
[]23.0999999912455684.104999941773713
{}22.67500001471489714.559999923221767
{}22.1449999371543534.895000020042062
{}21.9849999994039544.94999997317791
new Date()258.73000000137836287.6300000352785
new Date()261.01499993819743277.97000005375594
new Date()253.14499996602535288.19500003010035
/(?:)/23.58500007539987618.449999974109232
/(?:)/22.7900000754743818.22499989066273
/(?:)/23.3100000768899918.70499993674457
[1, 2]25.67000000271946219.23000009264797
[1, 2]25.5650000181049118.500000005587935
[1, 2]26.13000001292675719.229999976232648
{a: "a"}31.41499997582286695.29000008478761
{a: "a"}32.5949999969452694.64499994646758
{a: "a"}31.19999996852129795.81999992951751
-0.121.4200000045821073.23000003118068
-0.121.949999965727333.2049999572336674
-0.121.4799999957904223.4150000428780913
NaN12.1250000083819033.060000017285347
NaN12.2099999571219093.05499997921288
NaN12.1100000105798243.0999999726191163
Infinity11.9599999161437152.8800000436604023
Infinity11.4199999952688812.8800000436604023
Infinity11.4549999125301842.9150000773370266
""12.92000000830739727.319999993778765
""13.42500001192092927.42000005673617
""13.66000005509704425.690000038594007
\13.2499999599531348.21999999694526
\12.19000003766268544.37499993946403
\12.86999997682869444.4300000090152
\t\n\r12.84999994095414982.82000001054257
\t\n\r14.33500007260590881.65499998722225
\t\n\r13.00000003539025882.50999997835606
19.3650000728666840.634999983012676
19.60500003769993840.18000001087785
19.49999993667006539.91000005044043
{x: 1, y: null, z: Array(4), g: Invalid Date}227.8350000269711657.8900000313297
{x: 1, y: null, z: Array(4), g: Invalid Date}224.61499995552003650.0249999808148
{x: 1, y: null, z: Array(4), g: Invalid Date}231.06000002007931652.959999977611

変換不要の undefined などはかなり速いです
typeof の if 文にマッチすれば return するだけですからね
それに続いて boolean 型と null は文字列変換するだけなのでこれも速いです
number 型は isFinite チェックをするので少し遅くなりますが それでも JSON.stringify より十分高速です

思ったより良い結果だったのですが 文字列型は JSON.stringify の方が速いです
JSON.stringify に速度が負けているところを見るとどれも文字列型が含まれています
replace でのエスケープ処理はやっぱり時間がかかるようで native レイヤで実装されてる JSON.stringify のほうが有利のようです
文字列型を含まない配列なら JSON.stringify より速いですが オブジェクトになると key 部分で必ず文字列としてのエスケープ処理が必要なので JSON.stringify の方が速くなります

実際の使用を考えると 変換対象は大きめなオブジェクトが多く key の数も多くなります
数値や null などがいくら速くても文字列部分が遅いとほとんどのケースでは JSON.stringify の方が速くなるはずです
実用には向いていませんね

しかし 遅いのは文字列型の変換のみなら 文字列の変換だけを JSON.stringify でやれば良いように思います
文字列の変換を jsonstr 関数で行っているのでこれを JSON.stringify に置き換えます

const json = (value) => {
const type = typeof value
if (type === "undefined" || type === "symbol" || type === "function") return
if (type === "boolean" || value === null) return String(value)
if (type === "number") return String(isFinite(value) ? value : null)
if (type === "string") return JSON.stringify(value)
if (value.toJSON) return json(value.toJSON())

if (Array.isArray(value)) {
let values = ""
for (const item of value) {
const v = json(item)
if (v !== undefined) values += "," + v
}
return "[" + values.slice(1) + "]"
}

if (typeof value === "object") {
let values = ""
for (const [key, item] of Object.entries(value)) {
const v = json(item)
if (v !== undefined) values += "," + JSON.stringify(key) + ":" + v
}
return "{" + values.slice(1) + "}"
}

throw new Error("unexpected")
}

文字列関係の部分の結果を取り出すとこういう感じになりました

valueJSON.stringify()json()
""13.24000000022351715.02499997150153
""13.67999997455626714.7450000513345
""14.8099999641999615.110000036656857
\13.39500001631677214.474999974481761
\13.3549999445676815.28000005055219
\12.84500001929700413.860000064596534
22.3649999825283921.610000054351985
20.3100000508129621.864999900572002
21.7500000726431620.76500002294779
{a: "a"}46.36499995831465.99499995354563
{a: "a"}35.185000044293744.569999910891056
{a: "a"}35.1200000150129244.43500004708767
{x: 1, y: null, z: Array(4), g: Invalid Date}231.11000005155802514.7599999327213
{x: 1, y: null, z: Array(4), g: Invalid Date}237.26500000339536.5900000324473
{x: 1, y: null, z: Array(4), g: Invalid Date}238.54000004939735466.429999913089

直接 JSON.stringify するよりも余計な処理があるので 文字列型だけの場合は JSON.stringify より少し遅いです
オブジェクトを見ると 元よりは高速化していますが JSON.stringify より遅いままです
それに文字列だけのときに比べて差が大きいです
文字列型のエスケープ以外にも Object.entries が遅いなどあるのでしょうね

JSON.stringify は遅いといっても 組み込み関数だけあって JavaScript で実装して速度を上回れるものでもなかったです
基本的には JSON.stringify で それ以上に速度が求められることがあれば スキーマ定義して Fastify で使われてるライブラリを使うのが良さそうです