node.jsでYAMLに記述した式を評価して変換してみた

渡辺です。 テストデータのメンテは大変なんです。

お題

こんな定義があったとします。

const locations = {
    ENTRANCE: [100, 110],
    EXIT: [500, 510]
};

これを踏まえて、YAMLでこんなデータを定義したいです。

- person_id: 101
  x: 100 # ENTRANCE[0]
  y: 110 # ENTRANCE[1]
- person_id: 102
  x: 110 # ENTRANCE[0] + 10
  y: 120 # ENTRANCE[1] + 10
- person_id: 103
  x: 500 # EXIT[0]
  y: 510 # EXIT[1]

locations は変わる可能性があるので、変わったときに再計算すると辛いんです。 YAMLに式が書きたいんです。

解決方法を考えてみましょう。 YAMLはフォーマットを少しいじっても構いませんが、YAMLとして読み込める(valid)であることは条件です。

.

.

.

.

.

解答例

こうやってみました。

- person_id: 101
  x: <ENTRANCE[0]>
  y: <ENTRANCE[1]>
- person_id: 102
  x: <ENTRANCE[0] + 10>
  y: <ENTRANCE[1] + 10>
- person_id: 103
  x: <EXIT[0]>
  y: <EXIT[1]>
const fs = require('fs');
const YAML = require('yaml');

const locations = {
    ENTRANCE: [100, 110],
    EXIT: [500, 510]
};
let data = YAML.parse(fs.readFileSync(`./data.yaml`, 'utf8'));

const LOC = Object.keys(locations)
                  .map(key => (`let ${key}=${JSON.stringify(locations[key])}`))
                  .join(';');
const conv = (data) => {
    const _conv = (obj) => {
        Object.keys(obj).forEach(key => {
            if (/^<.+>$/.test(obj[key])) {
                let value = obj[key].substring(1, obj[key].length - 1);
                obj[key] = eval(`${LOC};${value}`);
            } else if (typeof obj[key] === 'object') {
                _conv(obj[key]);
            }
        });
    };
    Object.keys(data).forEach(key => _conv(data[key]));
    return data;
};

console.log(JSON.stringify(locations, null, 2));
console.log(JSON.stringify(data, null, 2));
console.log('----');
console.log(JSON.stringify(conv(data), null, 2));

実行結果

{
  "ENTRANCE": [
    100,
    110
  ],
  "EXIT": [
    500,
    510
  ]
}
[
  {
    "person_id": 101,
    "x": "<ENTRANCE[0]>",
    "y": "<ENTRANCE[1]>"
  },
  {
    "person_id": 102,
    "x": "<ENTRANCE[0] + 10>",
    "y": "<ENTRANCE[1] + 10>"
  },
  {
    "person_id": 103,
    "x": "<EXIT[0]>",
    "y": "<EXIT[1]>"
  }
]
----
[
  {
    "person_id": 101,
    "x": 100,
    "y": 110
  },
  {
    "person_id": 102,
    "x": 110,
    "y": 120
  },
  {
    "person_id": 103,
    "x": 500,
    "y": 510
  }
]

解説

式かどうか解るように <> で囲みました。 式部分を評価する時に、 eval を利用します。 この式の中で使う変数は、 locations から作成します。 LOC はゴニョゴニョ変換して、こんな感じ。

let ENTRANCE=[100,110];let EXIT=[500,510]

なので、

> eval('let ENTRANCE=[100,110];let EXIT=[500,510];ENTRANCE[0] + 10')
110

他になにか解決方法ないですかね?