JarvizのJSONLフォーマット

Jarviz(jarviz-graph)は1行1JSONオブジェクトの JSONL(JSON Lines) 形式でCall Graph情報を受け取ります。 各行に呼び出し関係を1エッジとして記録します。

JSONL — フォーマット仕様(Java版)
{"appSetName":"myapp","artifactFileName":"myapp.jar","packageName":"com.example","className":"OrderService","methodName":"placeOrder","callerPackageName":"com.example","callerClassName":"OrderController","callerMethodName":"submitOrder"}
{"appSetName":"myapp","artifactFileName":"myapp.jar","packageName":"com.example","className":"PaymentService","methodName":"charge","callerPackageName":"com.example","callerClassName":"OrderService","callerMethodName":"placeOrder"}

6つの主要フィールドの意味は以下の通りです。

フィールドJava での意味COBOL でのマッピング
appSetNameアプリケーション名システム名(固定値)
artifactFileNameJARファイル名COBOLソースファイル名
packageNameパッケージ名Division("PROCEDURE"固定)
classNameクラス名プログラム名(PROGRAM-ID)
methodName呼び出され側メソッド名呼び出され側Paragraph名
callerClassName呼び出し元クラス名呼び出し元プログラム名
callerMethodName呼び出し元メソッド名呼び出し元Paragraph名

COBOLとJSONLフィールドのマッピング

COBOLの PERFORM PROCESS-PARA(MAIN-PARA から)を例に取ると、 以下のようにJSONLの1行に変換されます。

JSONL — COBOL向けエッジ例
{"appSetName":"BILLING-SYSTEM","artifactFileName":"SAMPLE-PGM.cbl","packageName":"PROCEDURE","className":"SAMPLE-PGM","methodName":"PROCESS-PARA","callerPackageName":"PROCEDURE","callerClassName":"SAMPLE-PGM","callerMethodName":"MAIN-PARA"}

全体スクリプト

PART 03 で作成したCall Graph構築関数を利用して、 COBOLソースファイル群をJSONLに一括変換するスクリプトです。

Python — COBOL→JSONL 変換スクリプト
import json
import re
import sys
from pathlib import Path
from collections import defaultdict

APP_SET_NAME = 'BILLING-SYSTEM'   # ← システム名を変更してください

# ─────────────────────────────────────────────
# Paragraph 抽出
# ─────────────────────────────────────────────
def parse_paragraphs(text: str) -> list[str]:
    proc_match = re.search(r'PROCEDURE\s+DIVISION', text, re.IGNORECASE)
    if not proc_match:
        return []
    proc_text = text[proc_match.start():]
    para_pattern = re.compile(r'^       ([A-Z0-9][A-Z0-9\-]*)\.\s*$', re.MULTILINE)
    return [p for p in para_pattern.findall(proc_text) if p != 'DIVISION']

# ─────────────────────────────────────────────
# PERFORM エッジ抽出
# ─────────────────────────────────────────────
def parse_perform_edges(text: str, paragraphs: list[str]) -> list[tuple]:
    para_set = set(paragraphs)
    edges = []
    proc_match = re.search(r'PROCEDURE\s+DIVISION', text, re.IGNORECASE)
    if not proc_match:
        return []
    proc_text = text[proc_match.start():]
    current_para = None
    keywords = {'UNTIL', 'VARYING', 'THROUGH', 'THRU', 'TIMES', 'WITH', 'TEST'}
    for line in proc_text.splitlines():
        para_match = re.match(r'^       ([A-Z0-9][A-Z0-9\-]*)\.\s*$', line)
        if para_match:
            name = para_match.group(1)
            if name != 'DIVISION':
                current_para = name
            continue
        if current_para is None:
            continue
        perform_match = re.search(r'\bPERFORM\s+([A-Z0-9][A-Z0-9\-]*)', line, re.IGNORECASE)
        if perform_match:
            target = perform_match.group(1).upper()
            if target not in keywords and target in para_set:
                edges.append((current_para, target))
    return edges

# ─────────────────────────────────────────────
# PROGRAM-ID 抽出
# ─────────────────────────────────────────────
def parse_program_id(text: str, fallback: str) -> str:
    m = re.search(r'PROGRAM-ID\.\s+([A-Z0-9\-]+)', text, re.IGNORECASE)
    return m.group(1).upper() if m else fallback.upper()

# ─────────────────────────────────────────────
# メイン変換
# ─────────────────────────────────────────────
def cobol_to_jsonl(src_dir: Path, output_file: Path) -> None:
    records = []
    for cobol_path in sorted(src_dir.glob('**/*.cbl')):
        text = cobol_path.read_text(encoding='utf-8', errors='replace').upper()
        program_id = parse_program_id(text, cobol_path.stem)
        paragraphs = parse_paragraphs(text)
        edges = parse_perform_edges(text, paragraphs)

        for caller_para, callee_para in edges:
            records.append({
                'appSetName':        APP_SET_NAME,
                'artifactFileName':  cobol_path.name,
                'packageName':       'PROCEDURE',
                'className':         program_id,
                'methodName':        callee_para,
                'callerPackageName': 'PROCEDURE',
                'callerClassName':   program_id,
                'callerMethodName':  caller_para,
            })

    with output_file.open('w', encoding='utf-8') as f:
        for rec in records:
            f.write(json.dumps(rec, ensure_ascii=False) + '\n')

    print(f'✅ {len(records)} edges → {output_file}')

if __name__ == '__main__':
    src  = Path(sys.argv[1]) if len(sys.argv) > 1 else Path('src')
    out  = Path(sys.argv[2]) if len(sys.argv) > 2 else Path('cobol-callgraph.jsonl')
    cobol_to_jsonl(src, out)

出力例

cobol-callgraph.jsonl
{"appSetName":"BILLING-SYSTEM","artifactFileName":"SAMPLE-PGM.cbl","packageName":"PROCEDURE","className":"SAMPLE-PGM","methodName":"INIT-PARA","callerPackageName":"PROCEDURE","callerClassName":"SAMPLE-PGM","callerMethodName":"MAIN-PARA"}
{"appSetName":"BILLING-SYSTEM","artifactFileName":"SAMPLE-PGM.cbl","packageName":"PROCEDURE","className":"SAMPLE-PGM","methodName":"PROCESS-PARA","callerPackageName":"PROCEDURE","callerClassName":"SAMPLE-PGM","callerMethodName":"MAIN-PARA"}
{"appSetName":"BILLING-SYSTEM","artifactFileName":"SAMPLE-PGM.cbl","packageName":"PROCEDURE","className":"SAMPLE-PGM","methodName":"END-PARA","callerPackageName":"PROCEDURE","callerClassName":"SAMPLE-PGM","callerMethodName":"MAIN-PARA"}
{"appSetName":"BILLING-SYSTEM","artifactFileName":"SAMPLE-PGM.cbl","packageName":"PROCEDURE","className":"SAMPLE-PGM","methodName":"READ-PARA","callerPackageName":"PROCEDURE","callerClassName":"SAMPLE-PGM","callerMethodName":"PROCESS-PARA"}

複数プログラムのまとめ方

プログラム間CALL(CALL 'SUBPGM01')も同じJSONLに含めることで、 複数プログラムを横断したCall Graphを1ファイルに収められます。 この場合、callerClassName(呼び出し元プログラム)とclassName(呼び出し先プログラム)が異なる行が生成されます。

💡 Java版との混在

Java版で生成したJSONLとCOBOL版のJSONLを単純にcatコマンドで結合するだけで、言語をまたいだ3Dマップを生成できます。classNameにJavaのクラス名とCOBOLのPROGRAM-IDが混在しても、Jarvizはそのまま処理します。

出力の検証

Shell — JSONLの簡易検証
# 行数確認
wc -l cobol-callgraph.jsonl

# 各行が有効なJSONか検証
python -c "
import json, sys
errors = 0
with open('cobol-callgraph.jsonl') as f:
    for i, line in enumerate(f, 1):
        try:
            json.loads(line)
        except json.JSONDecodeError as e:
            print(f'Line {i}: {e}'); errors += 1
print(f'Errors: {errors}')
"

# callerClassName の一覧(プログラム一覧)
python -c "
import json
programs = set()
with open('cobol-callgraph.jsonl') as f:
    for line in f:
        d = json.loads(line)
        programs.add(d['callerClassName'])
for p in sorted(programs): print(p)
"

次の章では…

PART 05 では生成したJSONLをJarvizに読み込み、3Dの力線グラフで表示するセットアップ手順と、Java版との差異を解説します。

→ PART 05 Jarviz 3D可視化へ