AWS EC2 上で JSON Schema Draft 2020-12 + UN/CEFACT スキーマを検証する環境構築手順

Views: 2

本記事では、UN/CEFACT によって公開されている JSON Schema 定義と XBRL GL インスタンスを使用して、Amazon EC2 上に JSON Schema Draft 2020-12 準拠の検証環境を構築する手順を紹介します。

1. 前提環境の構築

1.1. OS と Node.js のバージョン確認

Amazon Linux 2 では fs/promisesajv/dist/2020 の読み込みなどに問題があったため、Amazon Linux 2023 を使用しました。

$ uname -a
Linux ip-xxx.ap-northeast-1.compute.internal ...
$ cat /etc/os-release
NAME="Amazon Linux"
VERSION="2023"

Node.js は以下のとおりバージョン 18.20.8 を使用:

$ node -v
v18.20.8

1.2. 必要なモジュールのインストール

$ npm install ajv@8.17.1 ajv-formats@3.0.1
Note

開発初期段階では以下のように @latest を指定して最新安定版を取得することも可能です。

$ npm install ajv@latest ajv-formats@latest

ただし、@latest を使用すると将来のバージョンアップによって動作が変わる可能性があります。
本記事では再現性を重視するため、動作確認済みのバージョンを明示的に指定しています。

2. UN/CEFACT スキーマの取得

2.1. GitHub からの取得方法(推奨)

$ git clone https://github.com/uncefact/spec-JSONschema.git
$ cd spec-JSONschema/JSONschema2020-12/library/BuyShipPay/D23B/

2.2. curl で個別取得する場合

$ mkdir -p schemas/meta schemas/codelists

$ curl -L -o schemas/meta/metadata.json \
  https://raw.githubusercontent.com/uncefact/spec-JSONschema/main/JSONschema2020-12/library/BuyShipPay/D23B/meta/metadata.json

$ curl -L -o schemas/UNECE-BasicComponents.json \
  https://raw.githubusercontent.com/uncefact/spec-JSONschema/main/JSONschema2020-12/library/BuyShipPay/D23B/UNECE-BasicComponents.json

$ curl -L -o schemas/codelists/UnitCode.json \
  https://raw.githubusercontent.com/uncefact/spec-JSONschema/main/JSONschema2020-12/library/BuyShipPay/D23B/codelists/UnitCode.json

2.3. Draft 2020-12 スキーマの取得

JSON Schema の $schema URI に指定される:
https://json-schema.org/draft/2020-12/schema

このメタスキーマは curl で取得時にリダイレクトされるため、必ず -L オプションを使用します。

$ curl -L -o schemas/draft2020-12.json https://json-schema.org/draft/2020-12/schema

-L を省略すると HTML が保存されてしまい、AJV が "Unknown format" というエラーでスキーマを処理できなくなります。

3. 検証スクリプト validate.js

以下は validate.js の主要部分です。

const Ajv2020 = require("ajv/dist/2020");
const addFormats = require("ajv-formats");
const fs = require("fs/promises");

const ajv = new Ajv2020({ loadSchema, strict: false });
addFormats(ajv);

3.1. $ref を解決する loadSchema 関数

async function loadSchema(uri) {
  if (uri === "http://json-schema.org/draft/2020-12/schema") {
    return JSON.parse(await fs.readFile("schemas/draft2020-12.json", "utf8"));
  }
  if (uri.endsWith("metadata.json")) {
    return JSON.parse(await fs.readFile("schemas/meta/metadata.json", "utf8"));
  }
  if (uri.includes("UNECE-BasicComponents")) {
    return JSON.parse(await fs.readFile("schemas/UNECE-BasicComponents.json", "utf8"));
  }
  if (uri.includes("UnitCode.json")) {
    return JSON.parse(await fs.readFile("schemas/codelists/UnitCode.json", "utf8"));
  }
  throw new Error("Unresolved schema: " + uri);
}

3.2. スキーマとインスタンスの検証実行

const schema = await ajv.compileAsync(
  JSON.parse(await fs.readFile("schemas/xbrl-gl-cor-schema.json"))
);
const data = JSON.parse(await fs.readFile("samples/xbrl-gl-instance.json"));

if (schema(data)) {
  console.log("✅ Validation passed");
} else {
  console.error("❌ Validation errors:", schema.errors);
}

4. metadata.json の役割と混乱

UN/CEFACT スキーマでは、"meta/metadata" のように $ref を通じてメタ情報を参照します。

誤ってファイル名を metadata.json ではなく meta-data.json として処理しようとすると、loadSchema() で正しく読み込めずに検証に失敗します。

"unece:metadata": {
  "$ref": "meta/metadata"
}

5. ディレクトリ構成例

project-root/
├── validate.js
├── schemas/
│   ├── draft2020-12.json
│   ├── meta/
│   │   └── metadata.json
│   ├── codelists/
│   │   └── UnitCode.json
│   ├── UNECE-BasicComponents.json
│   └── xbrl-gl-cor-schema.json
└── samples/
    └── xbrl-gl-instance.json

6. format 検証の有効化

以下のように format キーワードを使うと、ajv-formats により自動的に検証されます。

{
  "type": "string",
  "format": "date-time"
}

7. まとめ

  • Amazon Linux 2023 + Node.js v18.20.8 は Draft 2020-12 に適した環境

  • ajv@8.17.1ajv-formats@3.0.1 は Draft 2020-12 と format 検証に完全対応

  • GitHub の UN/CEFACT スキーマはローカル保存し $ref 解決経路を明示する

  • curl -L を忘れずに使用し、誤って HTML を保存しないよう注意

  • meta-data.json と誤記すると metadata.json$ref 解決に失敗するため注意が必要

8. 参考リンク

9. validate.js

const fs = require("fs").promises;
const path = require("path");
const Ajv2020 = require("ajv/dist/2020");
const addFormats = require("ajv-formats");

async function loadSchema(uri) {
  const base = path.resolve(__dirname, "schemas");

  // Draft 2020-12 メインスキーマ
  if (uri === "http://json-schema.org/draft/2020-12/schema") {
    const fullPath = path.join(base, "draft2020-12.json");
    const schema = JSON.parse(await fs.readFile(fullPath, "utf8"));
    delete schema["$id"];
    return schema;
  }

  // 各 vocabulary メタスキーマ
  const vocabularies = [
    "core",
    "applicator",
    "unevaluated",
    "validation",
    "meta-data",
    "format-annotation",
    "content"
  ];

  for (const name of vocabularies) {
    if (uri === `http://json-schema.org/draft/2020-12/meta/${name}`) {
      const fullPath = path.join(base, "meta", `${name}.json`);
      const schema = JSON.parse(await fs.readFile(fullPath, "utf8"));
      delete schema["$id"];
      return schema;
    }
  }

  // その他カスタムスキーマ
  if (uri.includes("UNECE-BasicComponents.json")) {
    const fullPath = path.join(
      __dirname,
      "../uncefact/spec-JSONschema/JSONschema2020-12/library/BuyShipPay/D23B/UNECE-BasicComponents.json"
    );
    return JSON.parse(await fs.readFile(fullPath, "utf8"));
  }

  if (uri.includes("codelists/")) {
    const fileName = path.basename(uri);
    const fullPath = path.join(
      __dirname,
      "../uncefact/spec-JSONschema/JSONschema2020-12/library/BuyShipPay/D23B/codelists",
      fileName
    );
    return JSON.parse(await fs.readFile(fullPath, "utf8"));
  }

  throw new Error(`Unknown schema URI: ${uri}`);
}

const ajv = new Ajv2020({
  loadSchema,
  strict: false
});
addFormats(ajv);

(async () => {
  try {
    const schemaPath = path.join(__dirname, "schemas/xbrl-gl-cor-schema.json");
    const instancePath = path.join(__dirname, "samples/xbrl-gl-instance.json");

    const schema = JSON.parse(await fs.readFile(schemaPath, "utf8"));
    const data = JSON.parse(await fs.readFile(instancePath, "utf8"));

    const validate = await ajv.compileAsync(schema);
    const valid = validate(data);

    if (valid) {
      console.log("✅ Validation successful: data is valid against the schema.");
    } else {
      console.error("❌ Validation errors:");
      console.error(validate.errors);
    }
  } catch (err) {
    console.error("💥 Runtime error:", err);
  }
})();


投稿日

カテゴリー:

, , ,

投稿者:

タグ:

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です