Here is an example of an MLIST usage, following up on the MLIST exercises from the 2016 Developers Conference: a set of routines to parse a JSON document into an MLIST. This was posted in the A-Shell forum under "MLIST - JSON."
The representation scheme chosen here is that each MLIST element contains one of the following:
code:
[] (sublist contains array)
{} (sublist contains object)
string (string scalar)
scalar (number, true, false, null)
"name":"string" (name:value pair)
"name":scalar (name:value pair)
"name":{} (value part of pair is object in sublist)
"name":[] (value part of pair is array in sublist)
So for the following JSON document (a common sample found on the Internet)...
code:
{ "store": {
"book": [
{ "category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{ "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{ "category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
... the MLIST representation would appear as shown below (using the Mlist'Multi'Level'Display() routine from mlistdemo.bsi; note that the ">" characters are just to indicate the nesting level) ...
code:
> {}
> "store":{}
> "book":[]
> {}
> "category":"reference"
> "author":"Nigel Rees"
> "title":"Sayings of the Century"
> "price":8.95
> {}
> "category":"fiction"
> "author":"Evelyn Waugh"
> "title":"Sword of Honour"
> "price":12.99
> {}
> "category":"fiction"
> "author":"Herman Melville"
> "title":"Moby Dick"
> "isbn":"0-553-21311-3"
> "price":8.99
> {}
> "category":"fiction"
> "author":"J. R. R. Tolkien"
> "title":"The Lord of the Rings"
> "isbn":"0-395-19395-8"
> "price":22.99
> "bicycle":{}
> "color":"red"
> "price":19.95
In other words, the JSON document was mapped onto an MLIST five levels deep:
• | The top level contains just one element - {} - i.e. a single JSON object. |
• | Attached to that parent node is a sublist consisting of of a single name:value pair, where the name is "store" and the value is another object, stored in its sublist. |
• | The contents of the store object at the next level down consists of two name:value pairs. The first is a "book", whose value is an array of books; the second is "bicycle", whose value is another object. |
• | Below "books":[] we have an array of objects, each of which has a sublist containing a series of name:value pairs representing attributes of the books. |
• | And so on. |
Representing the JSON document in an MLIST like this may not seem like a great simplification over the original text format, particularly in this case where the JSON was already nicely formatted for human reading—although in general, that isn't necessarily the case. The advantage the MLIST structure is that because it is normalized it is thus more amenable to the use of generic routines (possibly with custom callback functions) to perform routine operations such as walking the tree, searching, formatting, etc. The Mlist'Multi'Level'Display() display routine is a simple example of such a generic routine which is able to format with standard indentation any such document.
See the module FNMLISTJSN.BSI in SOSLIB:[907,10] for a set of ready-to-use MLIST/JSON helper routines, and MLISTJSON1.BP in EXLIB:[908,053] for a complete working example.
Although there are a couple of helper functions to read text from the file and grab the next token, the bulk of the logic translating between the JSON and MLIST representations is all in this relatively simple (but recursive) routine:
code:
!---------------------------------------------------------------------
!Function:
! parse a JSON array or object, up to final "]" or "}"
!Params:
! $json() (mlist) [ref] - parsed JSON data stored here (see notes at top)
! endtoken$ (s) [in] - ending token to look for ("]" or "}")
!Returns:
! >= 0 for success
! < 0 for error
!Module vars:
! chin, workbuf$
!Notes:
!---------------------------------------------------------------------
Function Fn'MLJ'Parse($json() as mlist(varstr), endtoken$ as s2:inputonly) as i4
map1 locals
map2 token$,s,0
map2 status,i,4
map2 bdone,BOOLEAN
map2 subendtoken$,s,2
level += 1
do
! get next [,{,],},name:{,name:[, or scalar from file
token$ = fn'mlj'next'token$()
switch token$
case "[" ! parse a new array
case "{" ! parse an object
$json(.pushback) = token$
subendtoken$ = ifelse$(token$="[","]","}")
status = Fn'MLJ'Parse($json(.back).sublist,subendtoken$)
if status >= 0 then
$json(.back) += subendtoken$
else
$json(.back) += " !!Error: missing terminator: "+subendtoken$
endif
exit
case "]"
case "}"
status = ifelse(token$=endtoken$,0,-1)
bdone = .TRUE
exit
case "" ! no more data (error?)
bdone = .TRUE
status = -1
exit
default
switch token$[-1,-1]
case "{" ! name:{
case "[" ! name:[
$json(.pushback) = token$
subendtoken$ = ifelse$(token$[-1,-1]="[","]","}")
status = Fn'MLJ'Parse($json(.back).sublist,subendtoken$)
if status >= 0 then
$json(.back) += subendtoken$
else
$json(.back) += " !!Error: missing terminator: "+subendtoken$
endif
exit
default
! add an name:value, or scalar element to our list
$json(.pushback) = token$
exit
endswitch
exit
endswitch
loop until bdone
if status < 0 then
Fn'MLJ'Parse = -1
endif
level -= 1
EndFunction