Node.jsを使っているとき、ログ出力やデバッグのためにオブジェクトを文字列に変換したいことがあります。今回、組み込みモジュールであるutilを使うことで、ちょっと強引ながらオブジェクトを文字列に変える方法を知ったので書き残しておきます。
この記事で紹介している実装はGitHubにおいてあります。 ソースコード全体を確認したい場合や実際に動かした場合は参考にしてください。
JavaScriptでオブジェクトを文字列にするときの問題点
JavaではtoStringメソッドがオーバーライドされていることが多く、欲しい情報が含まれた文字列にすることができます。しかし、Node.js(JavaScript)ではtoStringメソッドがオーバーライド実装されていることは稀で、[object Object]
といった意味のない文字列が返ってきます。
他には、JSON.stringify()
を使うことで文字列にできますが、エラーが発生するケースがあります。
下の実装のように循環している場合、TypeErrorがスローされます。
const obj = { message: 'this is object', }; obj.myself = obj; console.log(JSON.stringify(obj)); // TypeError: Converting circular structure to JSON
utilモジュールを使うとどうなるか
utilモジュールは先程の問題を解決してくれます。 また、utilモジュールはconsole.logでのオブジェクトの文字列化にも使われています。
この段落ではconsole.logとutilモジュールの2つのメソッドといくつかのオプションを使った文字列化を見ていきます。
今回、文字列にするオブジェクトは以下のとおりです。
const obj = { depth1: { depth2: { depth3: { depth4: { depth5: { depth6: 'deep', }, }, }, }, }, string: 'this is string', longString: "I thought what I'd do was, I'd pretend I was one of those deaf-mutes. I thought what I'd do was, I'd pretend I was one of those deaf-mutes. I thought what I'd do was, I'd pretend I was one of those deaf-mutes.", number: 123456, array: [ 1, 2, 3, 4, ], error: new Error('test error'), }; obj.myself = obj;
このオブジェクトの特徴としては、 - 最大で6つの階層があり、 - 長い文字列を値とするプロパティ、 - 数値を値とするプロパティ、 - 配列を値とするプロパティ、 - Errorオブジェクトを値とするプロパティ、 - 自分自身を参照した循環しているプロパティ、 が存在しています。
console.log
まず、参考としてutilモジュールを内部で使っていると言われているconsole.logによる文字列化を見ていきます。
console.log(obj);
結果:
オブジェクトの階層は3つまでは表示され、それ以上は[Object]
にされてしまいました。循環しているプロパティは[Circular *1]
と表示されました。
<ref *1> { depth1: { depth2: { depth3: [Object] } }, string: 'this is string', longString: "I thought what I'd do was, I'd pretend I was one of those deaf-mutes. I thought what I'd do was, I'd pretend I was one of those deaf-mutes. I thought what I'd do was, I'd pretend I was one of those deaf-mutes.", number: 123456, array: [ 1, 2, 3, 4 ], error: Error: test error at Object.<anonymous> (C:\mypath\nodejs-module-labo\util\index.js:22:12) at Module._compile (internal/modules/cjs/loader.js:1063:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10) at Module.load (internal/modules/cjs/loader.js:928:32) at Function.Module._load (internal/modules/cjs/loader.js:769:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12) at internal/main/run_main_module.js:17:47, myself: [Circular *1] }
util.format
では、utilモジュールのformatメソッドを使ってみます。
formatメソッドはprintf
のようにフォーマット指定子によって用意したパターンに値を渡すことで、オブジェクトを文字列にすることができます。
実装は以下のようになります。
const util = require('util'); // import * as util from 'util'; console.log(util.format('%o', obj));
結果:
オブジェクトの階層は5つまでは表示されるようになりました。それ以上はconsole.logと同じように[Object]
となっています。循環しているプロパティは同じく[Circular *1]
と表示されました。
配列では配列の長さが追加表示されています。Errorオブジェクトではstackとmessageが追加で表示されるようになりました。
配列とErrorオブジェクトの文字列化については冗長に見えます。
<ref *1> { depth1: { depth2: { depth3: { depth4: { depth5: [Object] } } } }, string: 'this is string', longString: "I thought what I'd do was, I'd pretend I was one of those deaf-mutes. I thought what I'd do was, I'd pretend I was one of those deaf-mutes. I thought what I'd do was, I'd pretend I was one of those deaf-mutes.", number: 123456, array: [ 1, 2, 3, 4, [length]: 4 ], error: Error: test error at Object.<anonymous> (C:\mypath\nodejs-module-labo\util\index.js:22:12) at Module._compile (internal/modules/cjs/loader.js:1063:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10) at Module.load (internal/modules/cjs/loader.js:928:32) at Function.Module._load (internal/modules/cjs/loader.js:769:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12) at internal/main/run_main_module.js:17:47 { [stack]: 'Error: test error\n' + ' at Object.<anonymous> (D:\\DocumentsD\\IT\\gitrepo\\nodejs-module-labo\\util\\index.js:22:12)\n' + ' at Module._compile (internal/modules/cjs/loader.js:1063:30)\n' + ' at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)\n' + ' at Module.load (internal/modules/cjs/loader.js:928:32)\n' + ' at Function.Module._load (internal/modules/cjs/loader.js:769:14)\n' + ' at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)\n' + ' at internal/main/run_main_module.js:17:47', [message]: 'test error' }, myself: [Circular *1] }
util.inspect
オブジェクトを検証するutilモジュールのinspectメソッドを使ってみます。 おそらくこのinspectメソッドがconsole.logの内部実装に使われているはずです。
const util = require('util'); // import * as util from 'util'; console.log(util.inspect(obj));
結果:
console.logと同じ結果が得られました。
<ref *1> { depth1: { depth2: { depth3: [Object] } }, string: 'this is string', longString: "I thought what I'd do was, I'd pretend I was one of those deaf-mutes. I thought what I'd do was, I'd pretend I was one of those deaf-mutes. I thought what I'd do was, I'd pretend I was one of those deaf-mutes.", number: 123456, array: [ 1, 2, 3, 4 ], error: Error: test error at Object.<anonymous> (C:\mypath\nodejs-module-labo\util\index.js:22:12) at Module._compile (internal/modules/cjs/loader.js:1063:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10) at Module.load (internal/modules/cjs/loader.js:928:32) at Function.Module._load (internal/modules/cjs/loader.js:769:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12) at internal/main/run_main_module.js:17:47, myself: [Circular *1] }
(おすすめ)util.inspectでオプションを使う
util.inpsectは第2引数にオプションを渡すことができ、文字列化の設定を変更することができます。 このやり方が今回記事で紹介しているオブジェクトの文字列化の方法としては一番良いと思っています。
今回はオプションを使って、 - これまで階層が3つもしくは5つまでしか表示されなかったのを全て表示されるように変更し、 - プロパティごとの改行をなくし、 - 1行あたりの表示文字数の制限をなくして 表示がコンパクトになるようにしてみました。
これによって、とりあえず行数をあまり使わずに情報を全部表示する、ということができます。
const util = require('util'); // import * as util from 'util'; console.log(util.inspect(obj, { depth: Infinity, breakLength: Infinity, compact: true, }));
結果:
階層が5以上になっても省略されることがなく、プロパティごとの改行もなくなりました。 ただし、Errorオブジェクトのスタックトレースはオプションを無視して改行されました。
<ref *1> { depth1: { depth2: { depth3: { depth4: { depth5: { depth6: 'deep' } } } } }, string: 'this is string', longString: "I thought what I'd do was, I'd pretend I was one of those deaf-mutes. I thought what I'd do was, I'd pretend I was one of those deaf-mutes. I thought what I'd do was, I'd pretend I was one of those deaf-mutes.", number: 123456, array: [ 1, 2, 3, 4 ], error: Error: test error at Object.<anonymous> (C:\mypath\nodejs-module-labo\util\index.js:22:12) at Module._compile (internal/modules/cjs/loader.js:1063:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10) at Module.load (internal/modules/cjs/loader.js:928:32) at Function.Module._load (internal/modules/cjs/loader.js:769:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12) at internal/main/run_main_module.js:17:47, myself: [Circular *1] }
(おまけ)JSON.stringify
おまけとしてJSON.stringifyを使ってエラーが投げられるパターンを載せておきます。
console.log(JSON.stringify(obj));
結果:
C:\mypath\nodejs-module-labo\util\index.js:52 console.log(JSON.stringify(obj)); // TypeError: Converting circular structure to JSON ^ TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' --- property 'myself' closes the circle at JSON.stringify (<anonymous>) at Object.<anonymous> (C:\mypath\nodejs-module-labo\util\index.js:52:18) at Module._compile (internal/modules/cjs/loader.js:1063:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10) at Module.load (internal/modules/cjs/loader.js:928:32) at Function.Module._load (internal/modules/cjs/loader.js:769:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12) at internal/main/run_main_module.js:17:47
utilモジュールのドキュメントの日本語訳
最後にutil.formatとutil.inspectのドキュメントの日本語訳を載せておきます。 この記事で使った実装以外を試すとき等に使ってもらえれば幸いです。
util.format(format[, ...args])
参考:https://nodejs.org/docs/latest-v18.x/api/util.html#utilformatformat-args
引数
format
: 文字列型。printf
のようなフォーマット文字列。args
: any型。文字列として表示する値。
戻り値
- 文字列型。フォーマットされた文字列。
util.format()
メソッドは、最初の引数をゼロ個以上のフォーマット指定子を含んだprintf
のようなフォーマット文字列として利用し、フォーマットされた文字列を返します。それぞれの指定子は対応する引数から変換された値に置換されます。
%o
:Object
。一般的なJavaScriptオブジェクトフォーマッティングによるオブジェクトの文字列表現。util.inspect()
を{ showHidden: true, showProxy: true }
オプションで利用したものと似ています。これは非列挙可能型のプロパティとプロキシを含む全オブジェクトを表示するでしょう。
util.inspect
https://nodejs.org/docs/latest-v18.x/api/util.html#utilinspectobject-options https://nodejs.org/docs/latest-v18.x/api/util.html#utilinspectobject-showhidden-depth-colors
util.inspect(object[, options])
util.inspect(object[, showHidden[, depth[, colors]]])
2種類の呼び出しがあり、1つ目のoptions
を使うほうで2つ目の呼び出しもカバーされています。
引数
object
: any型。JavaScriptのプリミティブ型またはObject
。options
: Object型。showHidden
: boolean型。表示するシンボルおよびプロパティを決定する。true
を指定すると、列挙不可能なシンボルおよびプロパティも表示する。デフォルトはfalse
。depth
: 数値型。引数object
を再帰的に表示するときに展開する階層数。全部展開するならばInfinity
またはnull
を指定する。デフォルトは2
。colors
: boolean型。出力にANSIカラーコード形式で色がつく。デフォルトはfalse
。customInspect
: boolean型。util.inspect.custom
プロパティには関数を設定することができ、表示をカスタマイズすることができるが、その関数を呼び出すか否かを決定できる。false
の場合、呼び出さない。デフォルトはtrue
。showProxy
: boolean型。JavaScriptのProxyオブジェクトを表示するとき、target
とhandler
も含めて表示するかどうかを決める。true
を指定すると表示する。デフォルトはfalse
。maxArrayLength
: integer型。配列を表示するさいの最大の要素数。配列が指定された数よりも多い要素数をもつ場合、その要素は表示されない。全て表示するにはInfinity
またはnull
を指定し、0
または負の数を指定した場合、全て表示されない。デフォルトは100
。maxStringLength
: integer型。表示する最大の文字数。全て表示するにはInfinity
またはnull
を指定し、0
または負の数を指定した場合、全て表示されない。デフォルトは10000
。breakLength
: integer型。改行する文字数を指定する(後述のbreakLength
とも連携)。すべて1行で表示するにはInfinity
を指定し、後述のcompact
にtrue
または1
以上の数値を設定する。デフォルトは80
。compact
: boolean型またはinteger型。表示をコンパクトにするかどうか、正確には表示するオブジェクトのプロパティごとに改行するか、指定された数だけを1行にして表示するかを決める(ただし、1行に表示する文字数はbreakLength
によっても制御される)。false
を指定するとプロパティごとに改行する。数値が指定された場合、その数のプロパティを表示するまでに1行の文字数が前述のbreakLength
の値を超えなければ改行せずに表示する。デフォルトは3
。sorted
: boolean型またはFunction型。プロパティ、エントリのソート順を決定する。true
またはFunction型を指定した場合、オブジェクトの全プロパティ、Set
およびMap
の全エントリをソートする。true
であればJavaScriptのデフォルトソート、Function型であればそれを使ってソートする。getters
: boolean型または文字列型。ゲッター・セッターの表示を決定する。true
が指定されるとゲッターを表示する。get
を指定するとセッターに対応していないゲッターだけを表示する。set
を指定するとセッターに対応しているゲッターだけを表示する。デフォルトはfalse
。numericSeparator
: boolean型。数値の表示形式を指定する。true
を指定すると、すべてのbigintとnumberを3桁ごとにアンダースコア(_
)で区切る。デフォルトはfalse
。
戻り値
- 文字列型。object
の文字列表現。
参考情報
- Util | Node.js Documentation
- Object.prototype.toString() - JavaScript | MDN
- JSON.stringify() - JavaScript | MDN
- javascript - How can I print a circular structure in a JSON-like format? - Stack Overflow
追記
オブジェクトを循環参照させたときに、無限ループにならないことを確認したので記事にしました。