JSON GENERATE and PARSE
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against ISO/IEC 1989:2023 (edition 3, January 2023; an upcoming 4th edition is in ballot), which this page does not rely on for JSON GENERATE and JSON PARSE (they are IBM statements that other compilers have followed); against IBM Enterprise COBOL for z/OS 6.5 (PTFs through September 2026) on z/OS 3.2, where both statements are fully supported; and against GnuCOBOL 3.2 (28 July 2023, the latest release), which has a partial JSON GENERATE, no working JSON PARSE, and with which every program on it was compiled with cobc -x -Wall and run on 2026-10-20 (and again with -std=ibm, which printed the same results unless the text says otherwise). z/OS examples are labelled "z/OS; not run here" and are checked against the Language Reference and the Programming Guide.
JSON is the text format that web services, REST clients and most modern tools exchange, and a batch program that cannot write it is stuck behind a conversion step. COBOL has two statements for it: JSON GENERATE turns a data item, usually a group, into a JSON document, and JSON PARSE fills a data item from one. This page covers both statements phrase by phrase, the two special registers, the way COBOL data becomes JSON text and back, encodings, null and boolean values, the Circulation daily summary exported as JSON, and the portable way to build JSON with STRING when the compiler has no JSON GENERATE. The statement STRING itself is on Strings: STRING, UNSTRING and INSPECT; national and UTF-8 data are on National, UTF-8 and Code Pages; the XML counterparts are on XML GENERATE and PARSE; exception handling in general is on Error Handling and Declaratives.
Where the book is outdated: Taulli’s Modern Mainframe Development (2022) does not describe JSON GENERATE or JSON PARSE at all, so everything here comes from the Language Reference, the Programming Guide and the tests run for this page. Its z/OS 2.4 context is also old; the statements, their special registers and the exception codes below are those of Enterprise COBOL 6.5.
What the two statements do
JSON GENERATE reads the source item field by field, converts each value to JSON text, names it after the data name (or a name you give), and stores the whole document in a receiving item. JSON PARSE runs the other way: it reads the document, matches each JSON name with a data name, and moves each value into the matching field with the rules of a MOVE.
Group items become JSON objects, elementary items become strings, numbers or the literals true, false and null, and an OCCURS table becomes an array. The text is plain JSON as defined by RFC 8259 (see below), with no blanks between the tokens.
Who supports what
The table shows what each compiler accepts. The GnuCOBOL column is what the 3.2 compiler did with the programs on this page; the 3.2 manual should be consulted for anything not listed.
| Feature | Enterprise COBOL 6.5 | GnuCOBOL 3.2 (tested) |
|---|---|---|
|
Supported |
Supported: group, elementary item, |
|
Supported |
Supported (counts bytes for an alphanumeric receiver) |
|
Supported from 6.5 |
Rejected: "'BYTES' is a reserved word, but isn’t supported" |
|
Supported, including |
Supported with literals; |
|
Supported, also with |
Supported for a named item; |
|
Supported |
Rejected: "syntax error, unexpected CONVERTING" |
|
Supported |
Rejected: syntax error |
|
Supported |
Rejected: syntax error |
|
Supported |
Supported; see the quirk with |
|
Arrays |
Warning: "OCCURS items in JSON/XML GENERATE is not implemented" |
|
Supported, with |
Compiles with the warning "JSON PARSE is not implemented" and does nothing |
|
Supported (set by |
Not meaningful without |
Programs that must run on both platforms therefore either confine themselves to the subset that 3.2 accepts, or build the document with STRING, as shown near the end of the page. Results from GnuCOBOL’s JSON GENERATE are also not guaranteed to match IBM’s in every detail; the fallback does not depend on either.
JSON GENERATE
The statement
The statement has a receiving item, a source item and a number of optional phrases, in this order:
JSON GENERATE receiver FROM source
[COUNT BYTES IN byte-count]
[COUNT [CHARACTERS] IN char-count]
[INDICATING item IS JSON NULL USING value IN indicator ...]
[ENCODING ccsid [FROM CODEPAGE]]
[NAME OF item IS "json-name" | OMITTED ...]
[SUPPRESS item | item WHEN ... | EVERY [NUMERIC|NONNUMERIC] WHEN ...]
[CONVERTING item TO JSON BOOLEAN|NULL USING value ...]
[ON EXCEPTION imperative-statement]
[NOT ON EXCEPTION imperative-statement]
[END-JSON]
The outline above simplifies the Language Reference syntax diagram, whose repetition marks do not reproduce in text; ALSO separates repeated INDICATING and CONVERTING entries, and NAME OF takes one or more item IS "name" pairs.
-
Receiver. An alphanumeric, national or UTF-8 item, elementary or group (a group is treated as an elementary item of its category). It must not be
JUSTIFIED, must not overlap the source, and must be big enough for the document: the Language Reference suggests two to three times the size of the source, because names and punctuation are added. On GnuCOBOL 3.2 the receiver may not be reference-modified ("JSON/XML GENERATE receiving item may not be reference modified"). On Enterprise COBOL 6.5 it may also be a dynamic-length (DYNAMIC) alphanumeric or UTF-8 item. -
Source. A group or elementary item, which cannot be reference-modified or have a
RENAMESclause.FILLERand unnamed items,REDEFINESitems and their subordinates, andRENAMESitems are ignored, so they do not appear in the output (the test below confirms it). Every other elementary item needs a usage other than pointers or object references, and the data names must be unique inside the source. -
Scope terminator.
END-JSONends the statement; a period also does.
How data becomes JSON
Each elementary item is converted in four steps: to character form, trimmed, converted to the target encoding, then escaped. The rules, taken from the Language Reference, with what GnuCOBOL 3.2 printed for the same item:
| Item | Rule | GnuCOBOL 3.2 result |
|---|---|---|
Unsigned or signed |
A JSON number; leading zeros removed (down to one digit before the point), no |
|
|
An explicit period for the decimal point, trailing zeros after the point kept |
|
|
|
|
|
Converted as if moved to an edited numeric item with the same digits |
|
Alphanumeric |
A string; trailing spaces removed (leading ones for a |
|
Alphanumeric item holding only spaces |
One space kept |
|
Numeric-edited item |
Not converted; it is text, so it becomes a string, leading space included |
|
Group item |
A JSON object named after the group, with its subordinates inside |
|
|
Ignored |
Not present |
Quote and backslash |
Escaped as |
|
Control characters (below U+0020) |
Short escapes for backspace, tab, line feed, form feed and carriage return ( |
A tab became |
The names come from the data names with the spelling used in the data description, so Run-Date in the program gives "Run-Date" (GnuCOBOL 3.2 kept the mixed case in a test), and uppercase data names such as the Circulation ones give uppercase names with hyphens. Those are rarely the names a client wants, which is why NAME OF exists. The top-level object is named after the source item, so a document built from DAILY-SUMMARY is {"DAILY-SUMMARY":{...}} unless that item is renamed or its name is made OMITTED on Enterprise COBOL.
The Circulation daily summary
The nightly Circulation run ends with an export step. This program reads LOANTRAN, BOOKMAST and MEMBMAST (the copybooks and files of Copybooks and REPLACE), counts the day’s checkouts, returns and rejects, counts the catalog, totals the fines, and writes the summary as one JSON line. It uses COUNT to know how long the document is, NAME OF to give the fields JSON-style names, and SUPPRESS to keep an internal tracing field out of the document.
* JSONSUM - Circulation daily summary exported as JSON
IDENTIFICATION DIVISION.
PROGRAM-ID. JSONSUM.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT BOOK-FILE ASSIGN TO "BOOKMAST"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT MEMBER-FILE ASSIGN TO "MEMBMAST"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT LOAN-FILE ASSIGN TO "LOANTRAN"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT JSON-FILE ASSIGN TO "SUMMARY.JSON"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD BOOK-FILE.
COPY BOOKREC.
FD MEMBER-FILE.
COPY MEMBREC.
FD LOAN-FILE.
COPY LOANREC.
FD JSON-FILE.
01 JSON-LINE PIC X(400).
WORKING-STORAGE SECTION.
01 EOF-FLAG PIC X VALUE "N".
88 AT-END VALUE "Y".
01 DAILY-SUMMARY.
05 RUN-DATE PIC 9(8) VALUE ZERO.
05 LIBRARY-NAME PIC X(30)
VALUE 'CENTRAL "MAIN" LIBRARY'.
05 LOAN-COUNTS.
10 CHECKOUTS PIC 9(4) VALUE ZERO.
10 RETURNS PIC 9(4) VALUE ZERO.
10 REJECTS PIC 9(4) VALUE ZERO.
05 CATALOG.
10 ACTIVE-TITLES PIC 9(4) VALUE ZERO.
10 WITHDRAWN-TITLES PIC 9(4) VALUE ZERO.
10 COPIES-ON-SHELF PIC 9(5) VALUE ZERO.
05 FINES-OUTSTANDING PIC S9(7)V99 VALUE ZERO.
05 TRACE-ID PIC X(8) VALUE "T0042".
01 JSON-TEXT PIC X(400).
01 JSON-LEN PIC 9(4) VALUE ZERO.
PROCEDURE DIVISION.
MAIN-PARA.
PERFORM COUNT-LOANS
PERFORM COUNT-BOOKS
PERFORM SUM-FINES
JSON GENERATE JSON-TEXT FROM DAILY-SUMMARY
COUNT IN JSON-LEN
NAME OF DAILY-SUMMARY IS "dailySummary"
RUN-DATE IS "runDate"
LIBRARY-NAME IS "library"
LOAN-COUNTS IS "loans"
ACTIVE-TITLES IS "activeTitles"
WITHDRAWN-TITLES IS "withdrawnTitles"
COPIES-ON-SHELF IS "copiesOnShelf"
FINES-OUTSTANDING IS "finesOutstanding"
CHECKOUTS IS "checkouts"
RETURNS IS "returns"
REJECTS IS "rejects"
CATALOG IS "catalog"
SUPPRESS TRACE-ID
ON EXCEPTION
DISPLAY "JSON GENERATE failed, JSON-CODE "
JSON-CODE
STOP RUN
END-JSON
DISPLAY "Generated " JSON-LEN " characters:"
DISPLAY JSON-TEXT(1:JSON-LEN)
OPEN OUTPUT JSON-FILE
MOVE JSON-TEXT(1:JSON-LEN) TO JSON-LINE
WRITE JSON-LINE
CLOSE JSON-FILE
STOP RUN.
COUNT-LOANS.
OPEN INPUT LOAN-FILE
PERFORM UNTIL AT-END
READ LOAN-FILE
AT END SET AT-END TO TRUE
NOT AT END
IF RUN-DATE = ZERO
MOVE LOAN-DATE TO RUN-DATE
END-IF
EVALUATE TRUE
WHEN LOAN-CHECKOUT ADD 1 TO CHECKOUTS
WHEN LOAN-RETURN ADD 1 TO RETURNS
WHEN OTHER ADD 1 TO REJECTS
END-EVALUATE
END-READ
END-PERFORM
CLOSE LOAN-FILE
MOVE "N" TO EOF-FLAG.
COUNT-BOOKS.
OPEN INPUT BOOK-FILE
PERFORM UNTIL AT-END
READ BOOK-FILE
AT END SET AT-END TO TRUE
NOT AT END
IF BOOK-ACTIVE
ADD 1 TO ACTIVE-TITLES
ELSE
ADD 1 TO WITHDRAWN-TITLES
END-IF
ADD BOOK-COPIES TO COPIES-ON-SHELF
END-READ
END-PERFORM
CLOSE BOOK-FILE
MOVE "N" TO EOF-FLAG.
SUM-FINES.
OPEN INPUT MEMBER-FILE
PERFORM UNTIL AT-END
READ MEMBER-FILE
AT END SET AT-END TO TRUE
NOT AT END ADD MEMBER-FINES TO FINES-OUTSTANDING
END-READ
END-PERFORM
CLOSE MEMBER-FILE.
Compiled with cobc -x -Wall -I. JSONSUM.cob (and with -std=ibm), no warnings, it prints:
Generated 0214 characters:
{"dailySummary":{"runDate":20261020,"library":"CENTRAL \"MAIN\" LIBRARY","loans":{"checkouts":3,"returns":1,"rejects":0},"catalog":{"activeTitles":3,"withdrawnTitles":1,"copiesOnShelf":7},"finesOutstanding":62.50}}
The file SUMMARY.JSON then holds the same line, and its pretty-printed form shows the structure:
{
"dailySummary": {
"runDate": 20261020,
"library": "CENTRAL \"MAIN\" LIBRARY",
"loans": { "checkouts": 3, "returns": 1, "rejects": 0 },
"catalog": { "activeTitles": 3, "withdrawnTitles": 1, "copiesOnShelf": 7 },
"finesOutstanding": 62.50
}
}
Points worth noticing:
-
COUNT IN JSON-LENgives the length of the document;JSON-TEXT(1:JSON-LEN)is the part to write, because the rest of the receiver is spaces. The count is in bytes for an alphanumeric receiver. -
The group names
LOAN-COUNTSandCATALOGbecame the nested objectsloansandcatalog, andTRACE-IDis missing because ofSUPPRESS. -
The quotes inside the library name were escaped, which makes the document valid; a hand-built string would have produced a broken one.
-
62.50is a JSON number. The value62.50has a trailing zero that JSON allows; leading zeros would not be allowed and the conversion removes them. -
The
WRITEto aLINE SEQUENTIALfile drops trailing spaces, so the file holds exactly the 214 characters and a line feed.
NAME, SUPPRESS and the other phrases
-
COUNT [CHARACTERS] IN. Receives the number of generated characters: double-bytes for a national receiver, Unicode code points for a UTF-8 receiver, bytes otherwise. It must be an integer item without aPin itsPICTURE. -
COUNT BYTES IN(Enterprise COBOL 6.5). Receives the number of bytes generated. It exists for UTF-8 receivers, whereCOUNTcounts code points and the byte length is what a file record or a message needs. GnuCOBOL 3.2 rejects it, and the byte length there is the plainCOUNT. -
NAME OF item IS "name". Replaces the JSON name of the source item or one of its subordinates. If the same item is named twice, the last name wins.OMITTEDgenerates an anonymous object or array, or a bare value, and is only valid for the source item itself: with a tableB1the statementJSON GENERATE out FROM b1 NAME OF b1 IS OMITTEDproduces a document that starts with[, which several JSON consumers expect. -
SUPPRESS. Excludes items.SUPPRESS itemremoves that item (and, for a group, everything in it); withWHENit removes an elementary item only when it equalsZERO,SPACE,LOW-VALUEorHIGH-VALUE;SUPPRESS EVERY [NUMERIC | NONNUMERIC] WHEN ...applies the test to all eligible items. A group is dropped only when all its items are dropped, but the outermost object is always written, even as{"name":{}}. The receiver must still be large enough for the document before suppression. -
CONVERTING item TO JSON BOOLEAN USING condition-name. The item must be a one-bytePIC Xitem, and the condition name (a level 88 directly under it) or a one-byte literal selectstrue; any other value givesfalse.CONVERTING item TO JSON NULL USING SPACE(orZERO,LOW-VALUE,HIGH-VALUE) generatesnullwhen the item equals the figurative constant. -
INDICATING item IS JSON NULL USING value IN indicator. A separate one-byte indicator item says when the indicated item must benull; the indicator itself does not appear in the document. -
ENCODING. See below. -
ON EXCEPTION. See below.
JSON-CODE and JSON-STATUS
Two special registers report what happened. JSON-CODE is defined as PIC S9(9) USAGE BINARY VALUE 0; it is zero after a successful statement and holds an exception code otherwise. JSON-STATUS is set by JSON PARSE for conditions that do not stop the statement (see the parse section). Both are always set, whether or not an ON EXCEPTION phrase is given. Without ON EXCEPTION, control simply continues after the statement, so the program must test JSON-CODE itself or it will use an incomplete document.
The exception codes of JSON GENERATE are:
JSON-CODE |
Meaning |
|---|---|
1 |
The receiver was too small. The |
2 |
The |
504 |
Not enough storage for the work area; increase the region size. |
507 |
Internal error; the Language Environment level may need its latest PTFs. |
500 to 599 (others) |
Internal error; report it to the support organization. |
When an exception occurs, generation stops and the contents of the receiver are undefined.
The next program makes both cases happen, a receiver of 20 bytes for a document that needs 43 and one of 200 bytes. It uses ON EXCEPTION alone and reads JSON-CODE afterwards.
* JSONERR - receiver too small: JSON-CODE 1
IDENTIFICATION DIVISION.
PROGRAM-ID. JSONERR.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 LOAN-COUNTS.
05 CHECKOUTS PIC 9(4) VALUE 3.
05 RETURNS PIC 9(4) VALUE 1.
01 SMALL-TEXT PIC X(20).
01 BIG-TEXT PIC X(200).
01 JSON-LEN PIC 9(4) VALUE ZERO.
PROCEDURE DIVISION.
MAIN-PARA.
JSON GENERATE SMALL-TEXT FROM LOAN-COUNTS
COUNT IN JSON-LEN
ON EXCEPTION
DISPLAY "small receiver: JSON-CODE " JSON-CODE
", count " JSON-LEN
END-JSON
JSON GENERATE BIG-TEXT FROM LOAN-COUNTS
COUNT IN JSON-LEN
ON EXCEPTION
DISPLAY "big receiver: JSON-CODE " JSON-CODE
END-JSON
DISPLAY "big receiver finished, JSON-CODE " JSON-CODE
", " BIG-TEXT(1:JSON-LEN)
STOP RUN.
Compiled with cobc -x -Wall JSONERR.cob (and with -std=ibm), no warnings, it prints:
small receiver: JSON-CODE +000000001, count 0043
big receiver finished, JSON-CODE +000000000, {"LOAN-COUNTS":{"CHECKOUTS":3,"RETURNS":1}}
JSON-CODE 1 is the value the manual gives. The count printed for the small receiver is 43, the length the document would have had, where the manual says IBM reports the positions actually generated; do not rely on the count after an exception on either platform, and check JSON-CODE instead.
A GnuCOBOL 3.2 quirk with NOT ON EXCEPTION
On Enterprise COBOL, ON EXCEPTION runs when an exception occurs and NOT ON EXCEPTION runs when none does. GnuCOBOL 3.2 behaved differently when the statement has both phrases: this program prints what each case ran.
* JSONQRK - ON EXCEPTION together with NOT ON EXCEPTION
IDENTIFICATION DIVISION.
PROGRAM-ID. JSONQRK.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 LOAN-COUNTS.
05 CHECKOUTS PIC 9(4) VALUE 3.
01 TEXT-OK PIC X(200).
01 TEXT-SMALL PIC X(10).
PROCEDURE DIVISION.
MAIN-PARA.
JSON GENERATE TEXT-OK FROM LOAN-COUNTS
ON EXCEPTION DISPLAY "ok: ON EXCEPTION ran"
NOT ON EXCEPTION DISPLAY "ok: NOT ON EXCEPTION ran"
END-JSON
DISPLAY "ok done, JSON-CODE " JSON-CODE
JSON GENERATE TEXT-SMALL FROM LOAN-COUNTS
ON EXCEPTION DISPLAY "small: ON EXCEPTION ran"
NOT ON EXCEPTION DISPLAY "small: NOT ON EXC. ran"
END-JSON
DISPLAY "small done, JSON-CODE " JSON-CODE
JSON GENERATE TEXT-SMALL FROM LOAN-COUNTS
NOT ON EXCEPTION DISPLAY "only-NOT: ran"
END-JSON
DISPLAY "only-NOT done, JSON-CODE " JSON-CODE
JSON GENERATE TEXT-OK FROM LOAN-COUNTS
NOT ON EXCEPTION DISPLAY "only-NOT ok: ran"
END-JSON
DISPLAY "only-NOT ok done, JSON-CODE " JSON-CODE
STOP RUN.
Compiled with cobc -x -Wall JSONQRK.cob (and with -std=ibm), no warnings, it prints:
ok done, JSON-CODE +000000000
small: ON EXCEPTION ran
small: NOT ON EXC. ran
small done, JSON-CODE +000000001
only-NOT done, JSON-CODE +000000001
only-NOT ok: ran
only-NOT ok done, JSON-CODE +000000000
With both phrases, a successful statement ran neither branch, and a failing one ran both. NOT ON EXCEPTION alone worked: it ran on success and did not run on failure. The portable pattern is therefore to omit NOT ON EXCEPTION and to test JSON-CODE after END-JSON, as JSONERR does; it behaves the same on both compilers.
Encoding, UTF-8 and RFC 8259
RFC 8259 is the specification of JSON text that the statements follow. The points that matter to a COBOL programmer:
-
Text exchanged between systems must be encoded in UTF-8 (section 8.1 of the RFC; the UTF-8 encoding itself is RFC 3629). A byte order mark must not be added.
-
A string must escape the quotation mark, the reverse solidus and all control characters from U+0000 to U+001F; other characters may be written as they are.
-
A number has no leading zeros (other than the single zero before a point), no
+sign, and no hexadecimal,NaNor infinity forms, and the literalstrue,falseandnullare lower case. This is why the conversion trims numbers and why aPIC Xitem holding007is a string, not a number. -
Names inside an object should be unique, and a top-level value can be any JSON value, not only an object.
On Enterprise COBOL the encoding of the generated text depends on the receiver and on ENCODING:
| Receiver | Default text | ENCODING values |
|---|---|---|
Alphanumeric |
UTF-8 (CCSID 1208) |
1208, or a single-byte EBCDIC CCSID such as 1047, 1140 or 37 (with |
National |
UTF-16 (CCSID 1200) |
1200 only |
|
UTF-8 |
1208 only |
The consequence on z/OS is that a PIC X receiver holds UTF-8 bytes, not EBCDIC characters. They are right for a file or a message sent to another system, and unreadable if the program DISPLAY`s them to the job log, which does not translate them. A program that wants a readable log line uses `ENCODING 1047 (or the CCSID of its CODEPAGE), or converts with FUNCTION DISPLAY-OF. With a UTF-8 or national receiver, COUNT counts code points or double-bytes while COUNT BYTES counts bytes: the title "Les Misérables" is 14 code points and 15 bytes in UTF-8. The same distinction is made on National, UTF-8 and Code Pages.
GnuCOBOL 3.2 has no ENCODING phrase and does no conversion: the bytes of the data item are copied into the document. On a UTF-8 system, a PIC X item that holds UTF-8 text (a source file in UTF-8 and a literal "Les Misérables") gave {"BOOK-JSON":{"BOOK-TITLE":"Les Misérables"}} with a COUNT of 46, which counts bytes, not characters. The same program with a file or data in a single-byte code page such as ISO 8859-1 would produce a document that is not valid UTF-8, so such data is converted before it is generated.
Null values and booleans
COBOL data has no null, so both statements need a rule for turning a value into null and back.
| Direction | Mechanism | Effect |
|---|---|---|
Generate |
|
|
Generate |
|
|
Generate |
|
|
Parse |
|
A |
Parse |
|
The figurative constant is moved to the item when the value is |
Parse |
|
|
Parse |
|
|
Parse |
none of the above |
A |
The Language Reference points out that not every item can be used with these phrases: the figurative constant must be a legal MOVE source for the item. GnuCOBOL 3.2 rejects all the phrases in the table.
Source: Enterprise COBOL for z/OS 6.5 Language Reference: JSON GENERATE statement; Language Reference: JSON PARSE statement
z/OS: booleans, nulls, encoding and COUNT BYTES
The following fragment exports one book as a JSON object for a catalog web service. It converts the status to a boolean, makes an unknown price null, produces UTF-8 in a dynamic-length receiver, and reads both counts. z/OS; not run here. It is checked against the syntax and rules of the Language Reference for 6.5.
01 BOOK-OUT.
05 BOOK-ID PIC 9(6).
05 BOOK-TITLE PIC X(30).
05 BOOK-STATUS PIC X.
88 BOOK-ACTIVE VALUE "A".
05 BOOK-PRICE PIC 9(4)V99.
05 PRICE-FLAG PIC X.
01 BOOK-JSON PIC U DYNAMIC LIMIT 400.
01 JSON-CHARS PIC 9(4) COMP.
01 JSON-BYTES PIC 9(4) COMP.
*
MOVE 1 TO BOOK-ID
MOVE "DATA STRUCTURES" TO BOOK-TITLE
MOVE "A" TO BOOK-STATUS
MOVE "Y" TO PRICE-FLAG
JSON GENERATE BOOK-JSON FROM BOOK-OUT
COUNT BYTES IN JSON-BYTES
COUNT IN JSON-CHARS
INDICATING BOOK-PRICE IS JSON NULL USING "Y"
IN PRICE-FLAG
NAME OF BOOK-OUT IS "book"
CONVERTING BOOK-STATUS TO JSON BOOLEAN
USING BOOK-ACTIVE
ON EXCEPTION
DISPLAY "JSON GENERATE failed, JSON-CODE " JSON-CODE
END-JSON
The rules give this document, with JSON-CHARS equal to JSON-BYTES because the text is pure ASCII:
{"book":{"BOOK-ID":1,"BOOK-TITLE":"DATA STRUCTURES","BOOK-STATUS":true,"BOOK-PRICE":null}}
Notes: PRICE-FLAG is not written because an indicator is not part of the document; a PIC U DYNAMIC receiver (UTF-8) is produced as UTF-8 and cannot be given an ENCODING other than 1208; and the clauses are written in the order the syntax diagram shows them.
JSON PARSE
The statement
JSON PARSE json-text INTO receiver
[WITH DETAIL]
[IGNORING JSON NULL FOR ALL | item ...]
[INDICATING item IS JSON NULL USING true-value AND false-value
[IN indicator] ...]
[ENCODING ccsid [FROM CODEPAGE]]
[NAME OF item IS "json-name" | OMITTED ...]
[SUPPRESS item ...]
[CONVERTING item FROM JSON BOOLEAN|NULL USING ...]
[ON EXCEPTION imperative-statement]
[NOT ON EXCEPTION imperative-statement]
[END-JSON]
-
The text. An alphanumeric, national or UTF-8 item, elementary or group, assumed to be UTF-8 unless
ENCODINGsays otherwise. It must not overlap the receiver. Unicode control characters, the quotation mark and the reverse solidus must be escaped in it (the escapes of the JSON specification are accepted, and so is\x, whichJSON GENERATEwrites for the EBCDIC new-line character). -
The receiver. A group or elementary item; the same restrictions as the source of
JSON GENERATE(noFILLER, noREDEFINEStargets, unique names). TheOCCURS DEPENDING ONobject of a table must be outside the receiver and is not updated by the statement. -
Matching. The nesting of the JSON objects must follow the nesting of the groups, names are matched with the data names case-sensitively (a JSON name such as
bookIdcan never match a hyphenated data name such asBOOK-ID, which is whyNAME OFis nearly always needed, and aNAMEliteral is compared case-sensitively), and the value is moved withMOVEsemantics, so5intoPIC 9(3)gives005and-9.0intoPIC S99V9gives a signed value. Arrays fillOCCURStables from the first element. -
SUPPRESS. Excludes an item and its subordinates from the assignment; it overridesNAME. -
WITH DETAIL. Issues runtime messages for the conditions that are not exceptions and for the exceptions (messages in theIGZ03range, listed in Appendix D of the Programming Guide), which is the way to see which name failed to match. Without it, only the special registers tell. -
Two names to take care of. A parse into a group needs the name of the top-level object to match the receiver’s name (or a
NAME OF receiver IS "..."), and theCODEPAGEcompiler option must name a single-byte EBCDIC CCSID.
JSON-CODE and JSON-STATUS after a parse
After JSON PARSE, JSON-CODE is zero or the exception code that stopped the statement, with the receiver partly modified. JSON-STATUS holds the sum of the codes for conditions that did not stop the statement, so test it by bit, not by equality. The Programming Guide shows a MOD and divide test; the comparison FUNCTION MOD(JSON-STATUS, 2 * code) >= code does the same without a fractional intermediate result.
JSON-STATUS bit |
Condition (not an exception) |
|---|---|
1 |
A data item had no matching JSON pair and was not changed |
2 |
A JSON pair matched no data item |
4 |
A data item had several matching pairs with the same value |
8 |
A table had more elements than the JSON array |
16 |
A JSON array had more values than the table |
32 |
An item was not changed because its value was |
64 |
Table elements were not changed because of |
128 |
A size error in a numeric assignment (the item was modified anyway) |
256 |
Loss of information in an alphanumeric assignment (modified anyway) |
512 |
A substitution character appeared in the conversion from Unicode to the |
JSON-CODE |
Exception (the statement stops) |
|---|---|
100 |
The JSON text was invalid |
101 |
The text was empty or only blanks |
102 |
Extra non-blank characters followed the closing brace of the outermost object |
103 |
An item had several matching pairs with different values; the leftmost was kept |
104 |
A value was incompatible with the matching data item |
105 |
A matching pair had the value |
106 |
No JSON pair matched any data item |
107 |
A |
108 |
An anonymous array was parsed without |
109 |
The |
The Circulation inquiry service receives a request object and the program parses it. z/OS; not run here. It is checked against the Language Reference syntax for 6.5.
01 REQUEST-TEXT PIC X(200).
01 INQUIRY-REQUEST.
05 BOOK-ID PIC 9(6).
05 MEMBER-ID PIC 9(6).
05 WANT-HOLD PIC X.
88 HOLD-REQUESTED VALUE "Y".
05 NOTE-TEXT PIC X(30).
05 AUDIT-TAG PIC X(8).
*
* REQUEST-TEXT holds, in UTF-8:
* {"inquiry":{"bookId":1,"memberId":100,"hold":true,
* "note":null,"extra":5}}
JSON PARSE REQUEST-TEXT INTO INQUIRY-REQUEST
WITH DETAIL
IGNORING JSON NULL FOR NOTE-TEXT
NAME OF INQUIRY-REQUEST IS "inquiry"
BOOK-ID IS "bookId"
MEMBER-ID IS "memberId"
WANT-HOLD IS "hold"
NOTE-TEXT IS "note"
SUPPRESS AUDIT-TAG
CONVERTING WANT-HOLD FROM JSON BOOLEAN
USING "Y" AND "N"
ON EXCEPTION
DISPLAY "request rejected, JSON-CODE " JSON-CODE
NOT ON EXCEPTION
IF FUNCTION MOD(JSON-STATUS, 4) >= 2
DISPLAY "request had a field we do not know"
END-IF
END-JSON
By the rules: BOOK-ID receives 000001, MEMBER-ID 000100, WANT-HOLD Y (from true), NOTE-TEXT is left as it was (the ignored null), AUDIT-TAG is not assigned, and the unknown extra pair sets JSON-STATUS bit 2 without an exception, with an informational runtime message because of WITH DETAIL. Had hold been missing, bit 1 would be set instead. A request whose text is {"other":1} would raise JSON-CODE 106, and a bookId of "abc" 104, so the ON EXCEPTION branch returns an error to the caller.
JSON PARSE on GnuCOBOL 3.2
GnuCOBOL 3.2 recognises the statement and does nothing with it. This program parses the same kind of request:
* JSONPRS - JSON PARSE on GnuCOBOL 3.2
IDENTIFICATION DIVISION.
PROGRAM-ID. JSONPRS.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 INQUIRY-TEXT PIC X(80)
VALUE '{"inquiry":{"bookId":1,"copies":2}}'.
01 INQUIRY.
05 BOOK-ID PIC 9(6) VALUE 999999.
05 COPIES PIC 9(3) VALUE ZERO.
PROCEDURE DIVISION.
MAIN-PARA.
JSON PARSE INQUIRY-TEXT INTO INQUIRY
ON EXCEPTION
DISPLAY "parse failed, JSON-CODE " JSON-CODE
END-JSON
DISPLAY "BOOK-ID " BOOK-ID " COPIES " COPIES
STOP RUN.
Compiled with cobc -x -Wall JSONPRS.cob, it gives two warnings (the statement is flagged at its first line and at END-JSON), and the program still runs and leaves the receiver unchanged:
JSONPRS.cob: in paragraph 'MAIN-PARA':
JSONPRS.cob:13: warning: JSON PARSE is not implemented [-Wpending]
11 | PROCEDURE DIVISION.
12 | MAIN-PARA.
13 > JSON PARSE INQUIRY-TEXT INTO INQUIRY
14 | ON EXCEPTION
15 | DISPLAY "parse failed, JSON-CODE " JSON-CODE
JSONPRS.cob:16: warning: JSON PARSE is not implemented [-Wpending]
14 | ON EXCEPTION
15 | DISPLAY "parse failed, JSON-CODE " JSON-CODE
16 > END-JSON
17 | DISPLAY "BOOK-ID " BOOK-ID " COPIES " COPIES
18 | STOP RUN.
--- compiled (warnings above, if any) ---
BOOK-ID 999999 COPIES 000
Neither branch of ON EXCEPTION ran, JSON-CODE stayed zero, and the receiver kept its initial values, so a program that depends on JSON PARSE must not be run on 3.2 on the assumption that a warning-free run proves it worked. Inbound JSON on that platform is read with UNSTRING or INSPECT for simple fixed shapes (see Strings: STRING, UNSTRING and INSPECT), or by calling a C parser (see Interoperability with C, Java and Python).
The portable fallback: building JSON with STRING
When the compiler lacks JSON GENERATE (some do) or its phrases (CONVERTING, ENCODING, COUNT BYTES are not in 3.2), the document can be built with STRING. Three jobs have to be done by hand: edit numbers into JSON form (no leading zeros, a period, a minus sign), escape the text, and track the length with WITH POINTER. This program builds the same summary as JSONSUM, with a paragraph that escapes quotes, backslashes and control characters, and the numeric-edited items trimmed with FUNCTION TRIM.
* JSONSTR - the same summary built with STRING (portable)
IDENTIFICATION DIVISION.
PROGRAM-ID. JSONSTR.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT BOOK-FILE ASSIGN TO "BOOKMAST"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT MEMBER-FILE ASSIGN TO "MEMBMAST"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT LOAN-FILE ASSIGN TO "LOANTRAN"
ORGANIZATION IS LINE SEQUENTIAL.
SELECT JSON-FILE ASSIGN TO "SUMMARY2.JSON"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD BOOK-FILE.
COPY BOOKREC.
FD MEMBER-FILE.
COPY MEMBREC.
FD LOAN-FILE.
COPY LOANREC.
FD JSON-FILE.
01 JSON-LINE PIC X(400).
WORKING-STORAGE SECTION.
01 EOF-FLAG PIC X VALUE "N".
88 AT-END VALUE "Y".
01 RUN-DATE PIC 9(8) VALUE ZERO.
01 LIBRARY-NAME PIC X(30)
VALUE 'CENTRAL "MAIN" LIBRARY'.
01 CHECKOUTS PIC 9(4) VALUE ZERO.
01 RETURNS PIC 9(4) VALUE ZERO.
01 REJECTS PIC 9(4) VALUE ZERO.
01 ACTIVE-TITLES PIC 9(4) VALUE ZERO.
01 WITHDRAWN-TITLES PIC 9(4) VALUE ZERO.
01 COPIES-ON-SHELF PIC 9(5) VALUE ZERO.
01 FINES-OUTSTANDING PIC S9(7)V99 VALUE ZERO.
* numeric edited items: trimmed text for the JSON number
01 N-DATE PIC Z(7)9.
01 N-COUNT PIC Z(4)9.
01 N-COUNT2 PIC Z(4)9.
01 N-COUNT3 PIC Z(4)9.
01 N-ACTIVE PIC Z(4)9.
01 N-WITHDRAWN PIC Z(4)9.
01 N-COPIES PIC Z(4)9.
01 N-FINES PIC -(7)9.99.
* escaping of a text value
01 RAW-TEXT PIC X(30).
01 ESC-TEXT PIC X(90).
01 ESC-LEN PIC 99 COMP VALUE ZERO.
01 RAW-LEN PIC 99 COMP VALUE ZERO.
01 IDX PIC 99 COMP VALUE ZERO.
01 ONE-CHAR PIC X.
01 CODE-VAL PIC 999 COMP VALUE ZERO.
01 HI-NIBBLE PIC 99 COMP VALUE ZERO.
01 LO-NIBBLE PIC 99 COMP VALUE ZERO.
01 HEX-DIGITS PIC X(16) VALUE "0123456789abcdef".
01 JSON-TEXT PIC X(400).
01 PTR PIC 9(4) COMP VALUE 1.
PROCEDURE DIVISION.
MAIN-PARA.
PERFORM COUNT-LOANS
PERFORM COUNT-BOOKS
PERFORM SUM-FINES
MOVE LIBRARY-NAME TO RAW-TEXT
PERFORM ESCAPE-TEXT
MOVE RUN-DATE TO N-DATE
MOVE CHECKOUTS TO N-COUNT
MOVE RETURNS TO N-COUNT2
MOVE REJECTS TO N-COUNT3
MOVE ACTIVE-TITLES TO N-ACTIVE
MOVE WITHDRAWN-TITLES TO N-WITHDRAWN
MOVE COPIES-ON-SHELF TO N-COPIES
MOVE FINES-OUTSTANDING TO N-FINES
MOVE SPACES TO JSON-TEXT
MOVE 1 TO PTR
STRING '{"dailySummary":{"runDate":'
FUNCTION TRIM(N-DATE)
',"library":"' ESC-TEXT(1:ESC-LEN)
'","loans":{"checkouts":' FUNCTION TRIM(N-COUNT)
',"returns":' FUNCTION TRIM(N-COUNT2)
',"rejects":' FUNCTION TRIM(N-COUNT3)
'},"catalog":{"activeTitles":' FUNCTION TRIM(N-ACTIVE)
',"withdrawnTitles":' FUNCTION TRIM(N-WITHDRAWN)
',"copiesOnShelf":' FUNCTION TRIM(N-COPIES)
'},"finesOutstanding":' FUNCTION TRIM(N-FINES)
'}}'
DELIMITED BY SIZE
INTO JSON-TEXT WITH POINTER PTR
ON OVERFLOW
DISPLAY "JSON text does not fit"
STOP RUN
END-STRING
SUBTRACT 1 FROM PTR
DISPLAY "Built " PTR " characters:"
DISPLAY JSON-TEXT(1:PTR)
OPEN OUTPUT JSON-FILE
MOVE JSON-TEXT(1:PTR) TO JSON-LINE
WRITE JSON-LINE
CLOSE JSON-FILE
STOP RUN.
ESCAPE-TEXT.
MOVE SPACES TO ESC-TEXT
MOVE ZERO TO ESC-LEN
COMPUTE RAW-LEN = FUNCTION LENGTH(FUNCTION TRIM(RAW-TEXT
TRAILING))
PERFORM VARYING IDX FROM 1 BY 1 UNTIL IDX > RAW-LEN
MOVE RAW-TEXT(IDX:1) TO ONE-CHAR
COMPUTE CODE-VAL = FUNCTION ORD(ONE-CHAR) - 1
EVALUATE TRUE
WHEN ONE-CHAR = QUOTE OR ONE-CHAR = "\"
ADD 1 TO ESC-LEN
MOVE "\" TO ESC-TEXT(ESC-LEN:1)
ADD 1 TO ESC-LEN
MOVE ONE-CHAR TO ESC-TEXT(ESC-LEN:1)
WHEN CODE-VAL < 32
MOVE "\u00" TO ESC-TEXT(ESC-LEN + 1:4)
ADD 4 TO ESC-LEN
ADD 1 TO ESC-LEN
MOVE HEX-DIGITS(CODE-VAL / 16 + 1:1)
TO ESC-TEXT(ESC-LEN:1)
ADD 1 TO ESC-LEN
MOVE HEX-DIGITS(FUNCTION MOD(CODE-VAL, 16) + 1:1)
TO ESC-TEXT(ESC-LEN:1)
WHEN OTHER
ADD 1 TO ESC-LEN
MOVE ONE-CHAR TO ESC-TEXT(ESC-LEN:1)
END-EVALUATE
END-PERFORM.
COUNT-LOANS.
OPEN INPUT LOAN-FILE
PERFORM UNTIL AT-END
READ LOAN-FILE
AT END SET AT-END TO TRUE
NOT AT END
IF RUN-DATE = ZERO
MOVE LOAN-DATE TO RUN-DATE
END-IF
EVALUATE TRUE
WHEN LOAN-CHECKOUT ADD 1 TO CHECKOUTS
WHEN LOAN-RETURN ADD 1 TO RETURNS
WHEN OTHER ADD 1 TO REJECTS
END-EVALUATE
END-READ
END-PERFORM
CLOSE LOAN-FILE
MOVE "N" TO EOF-FLAG.
COUNT-BOOKS.
OPEN INPUT BOOK-FILE
PERFORM UNTIL AT-END
READ BOOK-FILE
AT END SET AT-END TO TRUE
NOT AT END
IF BOOK-ACTIVE
ADD 1 TO ACTIVE-TITLES
ELSE
ADD 1 TO WITHDRAWN-TITLES
END-IF
ADD BOOK-COPIES TO COPIES-ON-SHELF
END-READ
END-PERFORM
CLOSE BOOK-FILE
MOVE "N" TO EOF-FLAG.
SUM-FINES.
OPEN INPUT MEMBER-FILE
PERFORM UNTIL AT-END
READ MEMBER-FILE
AT END SET AT-END TO TRUE
NOT AT END ADD MEMBER-FINES TO FINES-OUTSTANDING
END-READ
END-PERFORM
CLOSE MEMBER-FILE.
Compiled with cobc -x -Wall -I. JSONSTR.cob, no warnings, it prints:
Built 0214 characters:
{"dailySummary":{"runDate":20261020,"library":"CENTRAL \"MAIN\" LIBRARY","loans":{"checkouts":3,"returns":1,"rejects":0},"catalog":{"activeTitles":3,"withdrawnTitles":1,"copiesOnShelf":7},"finesOutstanding":62.50}}
The file it wrote is byte-for-byte identical to the one JSONSUM wrote: cmp SUMMARY.JSON SUMMARY2.JSON printed nothing. With -std=ibm the output is the same except that the pointer prints with five digits (00214), because the binary item is then not truncated to its four-digit picture.
Limits of the fallback: it knows only the fields it is written for, so a new field means new code; it escapes control characters as \u00hh where IBM writes \t, which is equivalent JSON; and it does not convert code pages (the bytes are those of the program’s data, so under an EBCDIC compiler they would be EBCDIC and the consumer would have to expect that). For output that goes to non-COBOL consumers on z/OS, JSON GENERATE with its ENCODING is the better tool.
Common mistakes
-
Printing the whole receiver. The receiver is longer than the document and is padded with spaces. Use
COUNTand reference modification,receiver(1:count). -
A receiver that is too small. The statement fails with
JSON-CODE1 and the receiver is undefined. Size it at two to three times the source and always testJSON-CODE. -
Not testing
JSON-CODEwithoutON EXCEPTION. Control continues and the program uses an incomplete document. -
Using
NOT ON EXCEPTIONtogether withON EXCEPTIONon GnuCOBOL 3.2. Neither branch ran on success. TestJSON-CODEafterEND-JSON. -
Trusting the names. Data names such as
BOOK-IDare not the names an API client expects, and a parse ofbookIddoes not match them. UseNAME OF. -
Numeric-edited fields in the source. They become strings with their blanks and punctuation (
" 7.50"), not numbers. -
Assuming EBCDIC output. On z/OS the default output of a
PIC Xreceiver is UTF-8;DISPLAYdoes not translate it. -
Forgetting
COUNT BYTES. For a UTF-8 receiverCOUNTcounts code points, so a record length computed from it is too short for non-ASCII text. -
Checking
JSON-STATUSby equality. The codes are added together; test the bit. -
Running
JSON PARSEon GnuCOBOL 3.2. It compiles with a warning and does nothing. -
Unescaped text in a
STRING-built document. A single quote or backslash in a title makes the document invalid.
References
-
Enterprise COBOL for z/OS 6.5 Language Reference: JSON GENERATE statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: JSON PARSE statement
-
Enterprise COBOL for z/OS 6.5 Language Reference: STRING statement
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Processing JSON input
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Generating JSON null values
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Handling JSON null values
-
Enterprise COBOL for z/OS 6.5 Programming Guide: Producing JSON output
-
Enterprise COBOL for z/OS 6.5 Programming Guide (PDF): Appendix D, JSON reference material
-
RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format