jq で複数の JSON ファイルを結合して書き出す

はじめに

ある日、会社の先輩が前に解約した Qiita Team の記事を見れたらと困っていた。各記事データは JSON ファイルにエクスポートして Dropbox にバックアップされている。このままでは不便なので、各記事の JSON ファイルを結合して HTML で各記事を見れるようにする。

検証環境

解説

jq とは、コマンドラインから JSON を操作できる JSON プロセッサです。公式サイト からダウンロード、またはパッケージ管理システムからインストールができます。

パッケージ管理システムには、Windows なら Chocolatey、macOS なら Homebrew などがあります。

私は macOS を利用しているので、下記の Homebrew コマンドで jq をインストールしました。

$ brew install jq

次に jq コマンドで複数の JSON ファイルを結合します。

コマンドの基本的な使い方は jq Manual に情報があります。

ターミナルを起動して JSON ファイルを保存しているディレクトリに移動、下記コマンドを実行するだけで JSON ファイルが結合されます。

$ cat *.json | jq . > articles.json

cat *.json は JSON ファイルを出力、
| は出力を次のコマンドへ引き渡し、
jq . は引き渡された出力を整形して出力、
> articles.json は出力をファイル(articles.json)にリダイレクト、
という命令になります。

下記コマンドを実行して、書き出した JSON ファイルの出力がオブジェクトの配列になっていれば成功です。

$ cat articles.json

あとは HTML に出力するだけです。

ご参考までに PHP 7.3.8Bootstrap 4.4.1 で下記のような簡易的なページを作りました。

記事一覧ページです。

<?php
$articles = json_decode(file_get_contents('articles.json'), true);
$article_count = 0;

usort($articles, function($a, $b) {
  return strtotime($b['created_at']) <=> strtotime($a['created_at'])
    ?: strtotime($b['updated_at']) <=> strtotime($a['updated_at']);
});
?>
<!DOCTYPE html>
<html>
<head>
  <meta name="robots" content="noindex">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body class="bg-dark text-white">
  <div class="container mt-4">
    <h1>Qiita Team Backup Articles</h1>
    <table class="table table-dark mt-4">
      <thead>
        <tr>
          <th scope="col">#</th>
          <th scope="col">タイトル</th>
          <th scope="col">作成者</th>
          <th scope="col">作成日</th>
          <th scope="col">更新日</th>
        </tr>
      </thead>
      <tbody>
        <?php foreach ($articles as $article): ?>
          <tr>
            <th scope="row"><?= ++$article_count ?></th>
            <td><a href="article.php?id=<?= $article['id'] ?>"><?= $article['title'] ?></a></td>
            <td><?= $article['user']['id'] ?></td>
            <td><?= date('Y/n/j', strtotime($article['created_at'])) ?></td>
            <td><?= date('Y/n/j', strtotime($article['updated_at'])) ?></td>
          </tr>
        <?php endforeach; ?>
      </tbody>
    </table>
  </div>
  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.bundle.min.js" integrity="sha384-6khuMg9gaYr5AxOqhkVIODVIvm9ynTT5J4V1cfthmT+emCG6yVmEZsRHdxlotUnm" crossorigin="anonymous"></script>
</body>
</html>

記事詳細ページです。

<?php
$articles = json_decode(file_get_contents('articles.json'), true);
$article = '';

$id = $_GET['id'] ?? '';
foreach ($articles as $v) {
  if ($id === $v['id']) $article = $v;
}
?>
<!DOCTYPE html>
<html>
<head>
  <meta name="robots" content="noindex">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
  <style>
  pre {
    background: #E5E7EA;
    color: black;
  }
  </style>
</head>
<body class="bg-dark">
  <div class="container mt-4 text-white">
    <nav aria-label="パンくずリスト">
      <ol class="breadcrumb">
        <li class="breadcrumb-item"><a href="./">ホーム</a></li>
        <li class="breadcrumb-item active" aria-current="page">ライブラリ</li>
      </ol>
    </nav>
    <span>@<?= $article['user']['id'] ?></span> 
    <small><?= date('Y年n月j日に更新', strtotime($article['updated_at'])) ?></small>
    <h1><?= $article['title'] ?></h1><hr>
    <?= $article['rendered_body'] ?>
  </div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.bundle.min.js" integrity="sha384-6khuMg9gaYr5AxOqhkVIODVIvm9ynTT5J4V1cfthmT+emCG6yVmEZsRHdxlotUnm" crossorigin="anonymous"></script>
</body>
</html>

サーバーにファイルをアップロードする前に、Basic 認証などのセキュリティ対策を忘れずに。

以上です。

おわりに

なければ作る。