Node-REDのChangeノード Deep Dive

# はじめに

みなさんフロー書いてますか！

Node-REDで処理フローを書く際に、できるだけ各種ノードを使って書く派と、ある程度の処理をFunctionノードに書いてしまう派があると思います。ノードを使って書く派のみなさんが愛用しているのが、本記事で紹介するChangeノードです。ChangeノードはNode-REDの良さが散りばめらていると思うのですが、[公式ドキュメントでもさらっと紹介](https://nodered.org/docs/user-guide/nodes#change) されているだけで、このノードの多機能さの割にあまり深く紹介された情報が少ないと感じます。

本記事は、「Node-REDのChangeノード Deep Dive」と題して、Changeノードのほぼ全機能を順に紹介していくことにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/97a17eaa-72a7-c874-8268-7b50bd5a2bee.png)


なお、本記事は 2022年11月2日に開催された[「Node-RED Park Vol.8 - Changeノード縛りの会！」](https://enebular.connpass.com/event/259821/) で私が発表した資料を基にしています。また、解説のベースは [Node-RED 3.0.2](https://github.com/node-red/node-red/releases/tag/3.0.2) を日本語設定で使っています。

（本記事では、今後、将来のバージョンで更新された機能は、できるだけ追記していきたいと思います）

# Changeノードの基本のキ

Changeノードは、Node-RED導入時にもれなくデフォルト導入されてる「コアノード(標準ノード)」の一つです。Changeノードはオブジェクトを、ノードプロパティで指定する１つ以上のルールに基づいて加工する機能を持っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/2a2919a9-8c46-c770-50e5-48f9b4685808.png)

新しく作成したChangeノード内には1つのルール（デフォルトは「値の代入」、msg.payloadが指定されています）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/87e5407d-7245-f7a7-91b4-0e8d2b2c3d6b.png)

ルールは、プロパティ左下の[+追加]ボタンをクリックすることで追加でき、ルール左側の削除ボタンをクリックすることで削除できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/db26a5ef-92aa-be37-046b-be1ca6852bcf.png)

指定されたルールは、上から順番に実行されます。ルールは上から実行されるので、１つのオブジェクトに対しての変更は、次のルールでは変更後の内容に対して「上書き(後勝ち)」で実施されます。
ルールの順番は、各ルールの横にある「ハンドル」をつかんでドラッグすることで入れ替えることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/beada699-f54f-14c8-5440-4071bb938284.png)

加工する対象のプロパティは、各ルールの上段に指定したオブジェクトのプロパティです。以下の3種類が指定可能です。
1. msgオブジェクト
1. flowコンテキスト
1. globalコンテキスト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/fc58c40d-5166-c993-7045-5eb4fcd565ce.png)

msgオブジェクトは、Changeノードの入力端子から与えられるオブジェクトです。また、flowコンテキスト/globalコンテキストは、コンテキストに対して実施されます。Node-REDのコンテキストについては、[「Node-REDにおけるコンテキストの活用について」](https://qiita.com/utaani/items/4f9b12f2ee6b1c5a620f)をご参照いただければと思います。

https://qiita.com/utaani/items/4f9b12f2ee6b1c5a620f


指定したオブジェクト内から、加工対象のプロパティを一つ、プロパティ名で指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/de22633f-22b7-553b-0aad-b537b7d64423.png)

# Changeノードの「ルール」

Changeノードのルールには、以下の4種類があります。
- 値の代入(set)
- 値の置換(change)
- 値の削除(delete)
- 値の移動(move)

ルール上段のメニューで指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/c6334736-86a3-3faa-6a1f-78da2d586067.png)

指定したルールの種類に応じて、ルール枠内のデザインが変更になります。

## ルール１：値の代入(set)

「値の代入」ルールは、上段で指定したプロパティに下段の「対象の値」に指定した値を代入します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/d6aaaae3-eb21-970c-6e76-67c5b999fc76.png)

代入できるプロパティは、以下のものです。

- msgオブジェクト
- flowコンテキスト
- globalコンテキスト
- 文字列
- 数値
- 真偽(Boolean型：true/false)
- JSON(JSONオブジェクト)
- バッファ(Bufferオブジェクト)
- 日時(タイムスタンプ値)
- JSONata式
- 環境変数
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/5cd6abd9-8466-7b37-61d3-c448c6bca101.png)

### オブジェクト/コンテキスト

msgオブジェクト/flowコンテキスト/globalコンテキストを指定する場合は、対象プロパティをフィールド内に指定します。ルールの実行時点で存在しないプロパティを指定することもできます。その時は、上段で指定したプロパティにはundefined値が代入されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/0c8466e3-81e3-a55d-8cd0-04ff70f63495.png)

### 定数

文字列/数値/真偽の場合は、定数をフィールド内に指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/7e31a124-0e88-81d0-4a13-7aa41b747940.png)

### JSONオブジェクト

JSONを指定し、フィールドにJSON文字列を記載することで、プロパティにオブジェクトを代入することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/95df0159-914d-abba-15c0-991a3752d04e.png)

フィールド右端の・・・をクリックして、JSONエディタで記載することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/eeb94fc5-2cd6-db3c-0e34-d35adc2b3587.png)

ace/monacaエディタで直接JSON文字列を記載するか、ビジュアルエディタタブで構造的に指定することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/bf8a52ae-bf7f-fe2a-b26c-b5215d67c89f.png)

空のJSONオブジェクトを「値の代入」に使うことで、プロパティをオブジェクトにすることができます。通常、いきなりmsg.payload.testのような、オブジェクトを持つプロパティに対して代入を実施することはできませんが、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/1ed9f313-d615-40e8-2a00-adbcf9f19288.png)

空のJSONオブジェクトを代入してから、プロパティのオブジェクト配下のプロパティを指定して代入することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/e32d60a9-376d-657b-a060-e604dc1c502d.png)

### バッファオブジェクト

バッファを指定して、Buffer型（バイナリバイト列）を代入できます。フィールドに直接、8bitバイナリ値からなる数値の配列を記入できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/1255ce8b-4211-8e1f-3d9a-01919f644cd3.png)

もしくは、フィールド右端の・・・をクリックして、バッファエディタで記載することもできます。上段に指定した文字列をUTF-8文字列として処理したバイナリバイト列が下段に表示(16進表示)され、代入するバイナリバイト列を作成できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/c10cefa1-4d31-9868-6c9f-0dca11197a60.png)

### 日時

日時を指定すると、Changeノード実行時のタイムスタンプ数値が代入されます。node.jsにおけるタイムスタンプは、UTC(協定世界時)での 1970年1月1日0時0分0秒からの経過時間をミリ秒単位で示したものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/1dfd5c49-24cf-6d69-95fb-9c138ccb4828.png)

### JSONata式

JSONata(JSON query and transformation language)は、JSONオブジェクトのデータ構造の照会と変換のための式言語です。階層構造を持つJSONオブジェクトから特定の階層や特定の名前を持つプロパティを取り出したり、JSONata組み込み関数を使って値を指定したりできます。フィールドに直接JSONata式を記入できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/f26c7859-75a2-34b5-f3ee-12ab97b56689.png)

もしくは、フィールド右端の・・・をクリックして、JSONata式エディタを開いて、下段の組み込み関数リファレンスを参照しながら、複雑な式を記載することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/c33ab332-e033-3ad3-28f2-1f3873499333.png)

Changeノードの強力なJSONata式機能を使うことで、Changeノード内でFunctionノードに準ずる柔軟性を持つデータの代入が実現できます。が、JSONata式は可読性が悪く、複雑なJSONata式を記載するとバグ混入のリスクもありますので、ある程度複雑なJSONata式での代入はFunctionノードで定義することをおすすめします。

JSONata式の詳細については、
公式サイト https://jsonata.org/ や
 [「JSONata の言語ガイドを訳してみた」](https://qiita.com/yamachan360/items/91805334890d18ef6e23)などを参照してください。

https://qiita.com/yamachan360/items/91805334890d18ef6e23


### 環境変数

環境変数は、Node-REDが動作するシステムやグループ、サブフロー内で定義される定数です。Node-RED起動時に引数で指定したり、settings.js内で指定することにより、Node-REDが動作している環境の情報をChangeノード内で活用することができます。

例えば、settings.jsの末尾に
```javascript:settings.js
process.env.TARGET_HOST='hogehoge.example.com';
```
と記載すると、Changeノードで
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/37d4c707-3578-74fd-ac96-2b6746501e94.png)
のように取得することができます。

サブフロー内では、「プロパティを編集」の中で環境変数を指定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/c953094c-9827-f0a3-ffb1-2bcc73078a0b.png)
当該サブフロー内では、指定した環境変数を使うことができます。


### 値のディープコピーについて

「値の代入」ルールでは、対象の値にmsgオブジェクト/flowコンテキスト/globalコンテキスト/環境変数を指定した場合、「対象の値」の下に「値のディープコピー」のチェックボックスが表示されます。チェックを入れると、オブジェクトの代入に「ディープコピー」が使われることになります。

「ディープコピー」は、配列やオブジェクトの完全複製を作成するものです。node.jsでは、通常の代入だとオブジェクトの複製は作成されず、元のオブジェクトの「別名」を作る感じになり、元のオブジェクトのプロパティを変更すると、代入先のプロパティも一緒に変更されてしまいます。

例えば、以下の例では、4番目のルールで、msg.payload2を作成する際に「値のディープコピー」にチェックを入れていませんので、フロー実行後の状態をデバッグノードで確認すると、元のmsg.payload.test1が"changed"になってしまっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/a18af724-3d54-c786-d7da-a845bb08cebb.png)

4番目のルールで「値のディープコピー」にチェックをすると、元のmsg.payload.test1は変更されなくなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/787a1082-d585-ba12-c50c-a9d387012af1.png)

## ルール２：値の置換(change)

「値の置換」ルールは、上段で指定したプロパティの文字列に対して、検索/置換を実施します。

以下の例では、1つ目のルールで定義した文字列の「CHANGEME」の部分を「これは」に置き換えています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/f428272e-268a-ee76-7cf6-55f120c46fbd.png)

また、以下の例では、msg.payload1とmsg.payload2を結合して、msg.payloadとしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/560d9084-a0b0-bcfe-dd93-c3f692f9b0f2.png)

（なお、↑の例は、msg.payload1に"payload2"という文字列が含まれる場合の問題を解決するため、5つ目のルールに後述する正規表現を使っています）

指定したプロパティが文字列でない場合、置換されません。指定したプロパティがオブジェクトの場合、そのオブジェクトに含まれるプロパティに文字列があっても、置換は実施されません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/d220b959-5dbb-ff6e-a1ee-9fc663355f2d.png)

「値の置換」で指定したmsgオブジェクト/flowコンテキスト/globalコンテキスト内の文字列プロパティに対して、中段「検索する文字列」に指定した文字列があれば、下段の「置換後の文字列」に指定した文字列で置き換えます。

中段「検索する文字列」には、以下の文字列プロパティを指定することができます。
- msgオブジェクト
- flowコンテキスト
- globalコンテキスト
- 文字列
- 正規表現
- 数値
- 真偽(Boolean型：true/false)
- 環境変数
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/08e91349-7eec-5609-71ae-0d03ee4b6f9e.png)


下段「置換後の文字列」には、以下の文字列プロパティを指定することができます。
- msgオブジェクト
- flowコンテキスト
- globalコンテキスト
- 文字列
- 数値
- 真偽(Boolean型：true/false)
- JSON(JSONオブジェクト)
- バッファ(Bufferオブジェクト)
- 環境変数
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/98df9d1c-81c6-09bb-8d3c-ca56a1ec699e.png)

「置換後の文字列」が文字列でない場合（数値/真偽/バッファオブジェクト等）できるだけ文字列に変換して置き換えようとします。JSONオブジェクトの場合は[object Object]という文字列になるので、注意が必要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/c6e6ffef-9a83-9367-8a5a-a6873998e637.png)


### 正規表現

標準では、「検索する文字列」で指定した文字列が複数回ある場合は、すべての文字列が「置換後の文字列」に置き換わります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/8b1f315c-0f4f-4787-ea82-babf401c1160.png)

中段「検索する文字列」で正規表現を指定した場合、node.jsの正規表現(regexp)が使われます。対象の文字列の位置や種類、繰り返しを指定し、柔軟な検索ができます。また、正規表現を指定した場合、下段「置換後の文字列」で$n（キャプチャグループ）を指定した置換ができます。

例えば以下の例では、URLの先頭のhttpをhttpsに書き換えています。先頭でない部分は書き換えず、また検索文字列で指定したhttpをつかって置換後の文字列を指定しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/003eef13-68dc-d167-9e47-0cabbd3813f0.png)


node.jsの正規表現については、以下のサイトを参照してください。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp

## ルール３：値の削除(delete)

「値の削除」は、オブジェクトから上段で指定したプロパティの削除を実施します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/b658ca06-2db4-909b-6347-e57c5e4708c7.png)

削除されるのは指定したプロパティのみで、その他のプロパティは保持されます。
なお、フローにオブジェクトが生成されるときに自動で作成される「msg._msgid」は、changeノードでは削除できません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/8e375948-5dd6-2430-6afd-71f1dce9aa68.png)


## ルール４：値の移動(move)

「値の移動」では、指定したオブジェクトのプロパティを別のオブジェクトのプロパティに移動させます。

以下の例では、msg.payloadをmsg.timestampに移動しています。「値の代入」とは逆に、上段で指定した値を下段に移す、という動作になるので、注意が必要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/25932/ee8ed824-1d4b-ad0f-3391-dfc019eb798a.png)

下段「対象の値」には、以下の文字列プロパティを指定することができます。
- msgオブジェクト
- flowコンテキスト
- globalコンテキスト

対象の値に指定したプロパティが存在した場合は、上書きされます。また同じオブジェクト内を指定した場合は、プロパティの名称を変更したようにふるまいます。


# 終わりに

Changeノードは標準ノードの中でも、「詳細なドキュメントがなくても、なんとなく使えば機能する」という、Node-REDの特徴を示すいいノードだと考えます。本記事で全貌を明らかにしましたが、今後も新機能がたくさん追加されていくことを期待します。

みなさま、よいNode-REDライフを！