>>SOURCE FORMAT IS FREE *> cobol-json-generate-parse.cob - turn a fixed-width account file into *> JSON Lines (one JSON object per line) with JSON GENERATE *> Dialect: GnuCOBOL 3.x, free format. JSON GENERATE needs a GnuCOBOL *> built with cJSON or JSON-C: the "JSON library" line of *> cobc --info must not say "disabled" or "not found". *> GnuCOBOL 3.2 has no working JSON PARSE (see the article). *> Status : desk-checked against the GnuCOBOL Programmer's Guide and the *> GnuCOBOL 3.2 release sources (cobc/parser.y, libcob/mlio.c) *> and test suite (tests/testsuite.src/run_ml.at); NOT compiled *> (no cobc on the machine this was written on) *> Input : datasets/cobol-json-generate-parse.dat, LINE SEQUENTIAL, *> 91 bytes per record (layout: code/cobol-json-generate-parse.cpy) *> Output : accounts.jsonl in the current folder; record 1 as raw JSON, *> the counts and the balance total on standard output *> Return : 0 normally, 8 if any record was not converted, 12 if a file *> will not open *> Build : cobc -x -I code code/cobol-json-generate-parse.cob *> Run : ./cobol-json-generate-parse (from the site folder) IDENTIFICATION DIVISION. PROGRAM-ID. ACCTJSON. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT Acct-File ASSIGN TO "datasets/cobol-json-generate-parse.dat" ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-Acct-Status. SELECT Json-File ASSIGN TO "accounts.jsonl" ORGANIZATION IS LINE SEQUENTIAL FILE STATUS IS WS-Json-Status. DATA DIVISION. FILE SECTION. FD Acct-File. COPY "cobol-json-generate-parse.cpy". FD Json-File. 01 Json-Line PIC X(300). WORKING-STORAGE SECTION. 01 WS-Acct-Status PIC XX. 01 WS-Json-Status PIC XX. 01 WS-Eof PIC X VALUE "N". 88 End-Of-File VALUE "Y". *> The JSON view of one account. Each field's PICTURE decides its JSON *> type: PIC X becomes a JSON string, a numeric PICTURE a JSON number. *> The branch is PIC X here so that its leading zeros survive. 01 Json-Account. 05 J-Id PIC X(8). 05 J-Holder PIC X(24). 05 J-Joint PIC X(24). 05 J-Branch PIC X(4). 05 J-Balance PIC S9(7)V99. 05 J-Opened PIC X(10). 05 J-Status PIC X(7). *> The receiving field, and the length JSON GENERATE reports for it. 01 WS-Json PIC X(300). 01 WS-Json-Len PIC 9(4). 01 WS-Counts. 05 WS-Read PIC 9(5) VALUE ZERO. 05 WS-Written PIC 9(5) VALUE ZERO. 05 WS-Failed PIC 9(5) VALUE ZERO. 05 WS-Longest PIC 9(4) VALUE ZERO. 01 WS-Total PIC S9(9)V99 VALUE ZERO. 01 WS-Show PIC ZZZZ9. 01 WS-Show-Money PIC -,---,---,--9.99. PROCEDURE DIVISION. Main-Para. OPEN INPUT Acct-File OUTPUT Json-File IF WS-Acct-Status NOT = "00" OR WS-Json-Status NOT = "00" DISPLAY "OPEN FAILED: ACCOUNTS " WS-Acct-Status " JSON " WS-Json-Status MOVE 12 TO RETURN-CODE STOP RUN END-IF PERFORM Read-Acct PERFORM UNTIL End-Of-File IF Acct-Branch IS NUMERIC AND Acct-Balance IS NUMERIC AND Acct-Opened IS NUMERIC IF WS-Read = 1 PERFORM Show-Raw-Json END-IF PERFORM Build-View PERFORM Write-Json ELSE ADD 1 TO WS-Failed DISPLAY "Record " WS-Read " skipped: a numeric field is not numeric" END-IF PERFORM Read-Acct END-PERFORM CLOSE Acct-File Json-File PERFORM Show-Counts IF WS-Failed > ZERO MOVE 8 TO RETURN-CODE END-IF STOP RUN. Read-Acct. READ Acct-File AT END SET End-Of-File TO TRUE NOT AT END ADD 1 TO WS-Read END-READ. *> Record 1, straight from the copybook layout. Every data name becomes a *> JSON name spelled as in the copybook, FILLER and the REDEFINES are *> skipped, numeric fields lose their leading zeros, and SUPPRESS keeps *> the tax reference out. END-DISPLAY matters: DISPLAY has its own *> [NOT] ON EXCEPTION phrases, and without it the NOT ON EXCEPTION *> below would belong to the DISPLAY instead of to JSON GENERATE. Show-Raw-Json. JSON GENERATE WS-Json FROM Acct-Rec COUNT IN WS-Json-Len SUPPRESS Acct-Tax-Ref ON EXCEPTION MOVE JSON-CODE TO WS-Show DISPLAY "Raw JSON GENERATE failed, JSON-CODE " WS-Show END-DISPLAY NOT ON EXCEPTION DISPLAY "Straight from the copybook:" DISPLAY WS-Json(1:WS-Json-Len) END-JSON. *> The JSON view of the current record: the branch keeps its leading *> zeros because J-Branch is PIC X, the date becomes ISO text, and the *> status letter becomes a word. Build-View. MOVE Acct-Id TO J-Id MOVE Acct-Holder TO J-Holder MOVE Acct-Joint TO J-Joint MOVE Acct-Branch TO J-Branch MOVE Acct-Balance TO J-Balance MOVE SPACES TO J-Opened STRING Acct-Opened-Year "-" Acct-Opened-Month "-" Acct-Opened-Day DELIMITED BY SIZE INTO J-Opened END-STRING EVALUATE TRUE WHEN Acct-Active MOVE "active" TO J-Status WHEN Acct-Dormant MOVE "dormant" TO J-Status WHEN Acct-Closed MOVE "closed" TO J-Status WHEN OTHER MOVE "unknown" TO J-Status END-EVALUATE. *> One JSON object per line. NAME OF gives each member its JSON name, and *> IS OMITTED drops the outer name so the line is a bare object. COUNT IN *> says how much of WS-Json was filled, so only the JSON text is written. Write-Json. JSON GENERATE WS-Json FROM Json-Account COUNT IN WS-Json-Len NAME OF Json-Account IS OMITTED, J-Id IS "accountId", J-Holder IS "holder", J-Joint IS "jointHolder", J-Branch IS "branch", J-Balance IS "balance", J-Opened IS "opened", J-Status IS "status" ON EXCEPTION ADD 1 TO WS-Failed MOVE JSON-CODE TO WS-Show DISPLAY "Record " WS-Read " not converted, JSON-CODE " WS-Show END-DISPLAY NOT ON EXCEPTION MOVE SPACES TO Json-Line MOVE WS-Json(1:WS-Json-Len) TO Json-Line WRITE Json-Line ADD 1 TO WS-Written ADD J-Balance TO WS-Total IF WS-Json-Len > WS-Longest MOVE WS-Json-Len TO WS-Longest END-IF END-JSON. Show-Counts. MOVE WS-Read TO WS-Show DISPLAY "Records read : " WS-Show MOVE WS-Written TO WS-Show DISPLAY "JSON lines written: " WS-Show MOVE WS-Failed TO WS-Show DISPLAY "Records failed : " WS-Show MOVE WS-Longest TO WS-Show DISPLAY "Longest line : " WS-Show MOVE WS-Total TO WS-Show-Money DISPLAY "Balance total : " WS-Show-Money.