FICO
FICO Xpress Optimization Examples Repository
FICO Optimization Community FICO Xpress Optimization Home
Back to examples browserPrevious exampleNext example

Defining a package to read JSON documents of unknown structure

Description
The package 'json' defines the new types 'jobj', 'jarr', 'jval' and 'jnull' for representing JSON documents of unknown structure and provides parsing functionality for reading JSON documents into these structures. Like jparse.mos it relies on the callback-based 'jsonparse' functionality of mmxnl.

The model file 'readjson.mos' shows three different methods of reading the JSON database file 'bookexamplesl.json' (documenting the example models from the book 'Applications of optimization with Xpress-MP') into Mosel structures, namely (1) representation as 'xmldoc' using mmxml functionality for loading JSON documents, (2) representation via specific user-defined record structures that are populated by a call to the mmhttp routine 'jsonread', and (3) using the types defined by the package 'json'. To run this example, you first need to compile the package (resulting in the BIM file 'json.bim'). You may then execute the example in the same directory that contains this BIM file.


Source Files
By clicking on a file name, a preview is opened at the bottom of this page.
json.mos[download]
readjson.mos[download]





json.mos

(!******************************************************
   Mosel Example Programs
   ======================

   file json.mos
   `````````````
   Package providing advanced union operators for 
   representing a JSON document

   (c) 2022 Fair Isaac Corporation
       author: Y. Colombani, Nov. 2021
*******************************************************!)
package json
version 0.1.0
uses 'mmxml','mmsystem','mmhttp'

! **** Package type definitions ****
public declarations
 !@doc.descr Type definition: JSON object
  jobj=array(string) of any
 !@doc.descr Type definition: JSON array
  jarr=array(range) of any
 !@doc.descr Type definition: a null value is stored as a string
  jnull=string
 !@doc.descr Type definition: JSON value
 !@doc.info A 'jval' is either a scalar of type real, text, boolean, or jobj, jarr or jnull.
  jval=text or real or boolean or jobj or jarr or jnull
end-declarations

! **** [private] Parser related data ****
!@doc.ignore
declarations
  afct:array(range) of any       ! jparser callbacks
  public jsctx=
    record
      l:list of jval             ! List of active elements
    end-record
  datafile="forjson"+newmuid     ! Unique identifier for temp. filename
  jstxt=newmuid                  ! Unique identifier for temp. output filename
end-declarations

! **** Public subroutines ****

(!@doc.
  @descr Retrieve the indexing set of a 'jobj' entity
  @param o JSON object
  @return indexing set (set of labels occurring in the JSON object)
!)
public function getfields(o:jobj):set of string
  returned:=o.index(1)
end-function

(!@doc.
  @descr Retrieve the indexing (range) set of a 'jarr' entity
  @param o JSON array
  @return index range set (position count of objects within the JSON array)
  @info Index numbering starts with the value 1.
!)
public function getrange(o:jarr):range
  returned:=o.index(1)
end-function

(!@doc.
  @descr Load a JSON document
  @param fname (extended) file name of a JSON document
  @param doc structure for storing the JSON contents 
  @return 0 if successful, 1 in case of parsing error.
  @info The entity 'doc' passed as argument is reset by this function.
!)
public function loadjson(fname:text,doc:jval):integer
  declarations
    ctx:jsctx
  end-declarations
  reset(doc)
  fopen(fname,F_INPUT)
  returned:=jsonparse(afct,ctx)  ! Invoke the JSON parsing
  fclose(F_INPUT)
  if ctx.l.size>0 then
    doc:=ctx.l(1)
  end-if
end-function

(!@doc.
  @descr Parse a text as a JSON document
  @param data text containing JSON data
  @param doc structure for storing the JSON contents 
  @return 0 if successful, 1 in case of parsing error.
  @info The entity 'doc' passed as argument is reset by this function.
  @related Invokes <fctRef>loadjson</fctRef>
!)
public function parsejson(data:text,doc:jval):integer
  publish(datafile,data)
  returned:=loadjson(text("text:")+datafile,doc)
  unpublish(datafile)
end-function

(!@doc.
  @descr Create a JSON representation in text form for a Mosel entity
  @param mosobj a Mosel entity
  @param flag optional format configuration (see documentation of 'jsonwrite')
!)
public function jsontext(mosobj:any):text
  publish(jstxt,returned)
  jsonwrite("text:"+jstxt,mosobj)
  unpublish(jstxt)
end-function

public function jsontext(mosobj:any, flag:integer):text
  publish(jstxt,returned)
  jsonwrite("text:"+jstxt,mosobj,flag)
  unpublish(jstxt)
end-function

!---------------------------Internal subroutines----------------------------

! jparser callback: open an object
function js_open_object(ctx:jsctx, name:text):integer
  if ctx.l.size>0 then
    with o=ctx.l(ctx.l.size) do
      if o is jobj then
        sname:=string(name)
        create(o.jobj(sname).jobj)
        ctx.l+=[o.jobj(sname).jobj]
      else   ! jarr
        create(o.jarr(o.jarr.size+1).jobj)
        ctx.l+=[o.jarr(o.jarr.size).jobj]
      end-if
    end-do
  else
    ctx.l+=[(jobj)]
  end-if
end-function

! jparser callback: close an object
function js_close_object(ctx:jsctx):integer
  if ctx.l.size>1 then
    cuttail(ctx.l,1)
  end-if
end-function

! jparser callback: open an array
function js_open_array(ctx:jsctx, name:text):integer
  if ctx.l.size>0 then
    with o=ctx.l(ctx.l.size) do
      if o is jobj then
        sname:=string(name)
        create(o.jobj(sname).jarr)
        ctx.l+=[o.jobj(sname).jarr]
      else   ! jarr
        create(o.jarr(o.jarr.size+1).jarr)
        ctx.l+=[o.jarr(o.jarr.size).jarr]
      end-if
    end-do
  else
    ctx.l+=[(jarr)]
  end-if
end-function

! jparser callback: close an array
function js_close_array(ctx:jsctx):integer
  if ctx.l.size>1 then
    cuttail(ctx.l,1)
  end-if
end-function

! jparser callback: a textual value
function js_text_val(ctx:jsctx, name:text, type:integer, val:text):integer
  if ctx.l.size>0 then
    with o=ctx.l(ctx.l.size) do
      if o is jobj then
        o.jobj(string(name)):=val
      else   ! jarr
        o.jarr(o.jarr.size+1):=val
      end-if
    end-do
  else
    ctx.l+=[val]
  end-if
end-function

! jparser callback: a numerical value
function js_num_val(ctx:jsctx, name:text, val:real):integer
  if ctx.l.size>0 then
    with o=ctx.l(ctx.l.size) do
      if o is jobj then
        o.jobj(string(name)):=val
      else   ! jarr
        o.jarr(o.jarr.size+1):=val
      end-if
    end-do
  else
    ctx.l+=[val]
  end-if
end-function

! jparser callback: a Boolean value
function js_bool_val(ctx:jsctx, name:text, val:boolean):integer
  if ctx.l.size>0 then
    with o=ctx.l(ctx.l.size) do
      if o is jobj then
        o.jobj(string(name)):=val
      else   ! jarr
        o.jarr(o.jarr.size+1):=val
      end-if
    end-do
  else
    ctx.l+=[val]
  end-if
end-function

! jparser callback: the 'null' value
function js_null_val(ctx:jsctx, name:text):integer 
  if ctx.l.size>0 then
    with o=ctx.l(ctx.l.size) do
      if o is jobj then
        o.jobj(string(name)):="null"
      else   ! jarr
        o.jarr(o.jarr.size+1):="null"
      end-if
    end-do
  else
    ctx.l+=["null"]
  end-if
end-function

! Module initialisation (definition of JSON parser callbacks)
 afct(JSON_FCT_OPEN_OBJ):=->js_open_object
 afct(JSON_FCT_CLOSE_OBJ):=->js_close_object
 afct(JSON_FCT_OPEN_ARR):=->js_open_array
 afct(JSON_FCT_CLOSE_ARR):=->js_close_array
 afct(JSON_FCT_TEXT):=->js_text_val
 afct(JSON_FCT_NUM):=->js_num_val
 afct(JSON_FCT_BOOL):=->js_bool_val
 afct(JSON_FCT_NULL):=->js_null_val

end-package

Back to examples browserPrevious exampleNext example