In <https://sourceforge.net/p/oorexx/discussion/general/thread/e0fc468371/?limit=25#5211/a31a/19b6/70b7/00a8> a problem with reading JSON data with json.cls was reported.

The enclosed "test.rex" creates two JSON files, one (test_01.json, 2.8 MB) with an array of 90.000 directory objects, one (test_02.json, 5.7 MB) with an array of 180.000 directory objects. It then reads the JSON files twice and creates the respective ooRexx array containing 90.000 or 180.000 directory objects with one entry ("Index n" with "Value n", where n=1 ... 90.000|180.000).

Here the timings on an older machine:

   G:\test\orx\json\perf\take2>test
   created test_01.json: 00:00:00.041000 wrote test_01.json: 00:00:00.003000
   created test_02.json: 00:00:00.087000 wrote test_02.json: 00:00:00.005000
   ---
   importing test_02.json ... *importing test_02.json lasted: 
/00:01:10.770000/* test_02.json:
   res~items: 180000 importing test_02.json ... *importing test_02.json lasted: 
00:06:03.864000*
   test_02.json: res~items: 180000
   importing test_01.json ... *importing test_01.json lasted: 00:02:15.433000* 
test_01.json:
   res~items: 90000 importing test_01.json ... *importing test_01.json lasted: 
/00:00:43.929000/*
   test_01.json: res~items: 90000

Repeating reading test_02.json lasts appr. five (!) times longer (/01:10.770/ vs.*06:03.864* minutes)! Also reading the first time test_01.json lasts five times longer than the second time (*02:15.433* vs. /00:43.929/). Also, the latest "json.cls" is enclosed (committed to trunk today), which seems to be able to read almost twice as fast as the release version (test.rex was carried out with the enclosed version of json.cls).

Now, the test data did not change, the code did not change, so it is strange that such a big variation can be observed (unless I miss something obvious).

So maybe there is some heavy effect on ooRexx creating 180.000 (90.000) directory objects and storing them in an array?

What might be the reason? What can be done about it?

---rony

/*----------------------------------------------------------------------------*/
/*                                                                            */
/* Copyright (c) 2010-2026 Rexx Language Association. All rights reserved.    */
/*                                                                            */
/* This program and the accompanying materials are made available under       */
/* the terms of the Common Public License v1.0 which accompanies this         */
/* distribution. A copy is also available at the following address:           */
/* https://www.oorexx.org/license.html                                        */
/*                                                                            */
/* Redistribution and use in source and binary forms, with or                 */
/* without modification, are permitted provided that the following            */
/* conditions are met:                                                        */
/*                                                                            */
/* Redistributions of source code must retain the above copyright             */
/* notice, this list of conditions and the following disclaimer.              */
/* Redistributions in binary form must reproduce the above copyright          */
/* notice, this list of conditions and the following disclaimer in            */
/* the documentation and/or other materials provided with the distribution.   */
/*                                                                            */
/* Neither the name of Rexx Language Association nor the names                */
/* of its contributors may be used to endorse or promote products             */
/* derived from this software without specific prior written permission.      */
/*                                                                            */
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS        */
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT          */
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS          */
/* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT   */
/* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,      */
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED   */
/* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,        */
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING    */
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS         */
/* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.               */
/*                                                                            */
/*----------------------------------------------------------------------------*/


/**
    An ooRexx utility class to encode and decode ooRexx objects in JSON (RFC 4627).
*/

::class "JSON" public

/** Returns the .JsonBoolean proxy for <code>.true</code>, which ensures a proper JSON encoding.
    It can be used interchangeably with ooRexx' <code>.true</code> or <code>1</code> values.

    @return the .JsonBoolean value for <code>.true</code>
*/
::attribute true     get class unguarded
  return .jsonBoolean~true

/** Returns the .JsonBoolean proxy for <code>.false</code>, which ensures a proper JSON encoding.
    It can be used interchangeably with ooRexx' <code>.false</code> or <code>0</code> values.

    @return the .JsonBoolean value for <code>.false</code>
*/
::attribute false    get class unguarded
  return .jsonBoolean~false



/**
 * Constructor, initilizes the instance.
 */
::method init
    expose eJS uJS ctrl crlf
    use strict arg      -- no arguments allowed

    eJS = .directory~new() -- escape Javascript
    eJS['08'x] = '\b'
    eJS['09'x] = '\t'
    eJS['0A'x] = '\n'
    eJS['0C'x] = '\f'
    eJS['0D'x] = '\r'
    eJS['"'] = '\"'
    eJS['\'] = '\\'
    eJS['/'] = '\/'

    uJS = .directory~new() -- unescape Javascript
    do index over eJS
        uJS[eJS[index]] = index
    end

    -- chars that end a value
    ctrl = .Set~of(' ', '}', ']', ',', '09'x, '0a'x, '0d'x)

    crlf = .rexxInfo~endOfLine   -- get the platform's endOfLine character(s)

    pkgLocal=.context~package~local
    pkgLocal~js.eJS  = eJS
    pkgLocal~js.uJS  = uJS
    pkgLocal~js.ctrl = ctrl
    pkgLocal~js.crlf = crlf


/** Utility class method to ease reading JSON files into an ooRexx object.
*
<p>Example:
<pre>
   rexxObject = .json~fromJsonFile('some.json')
</pre>
*
*  @param fn file name or file object to read JSON data from
*
* @return rexxObject ooRexx object representing the JSON text
*/
::method fromJsonFile class
  use strict arg fn

  s=.stream~new(fn)~~open("read")   -- open stream for reading
  jsonText=s~charin(1,s~chars)      -- read all chars, close stream
  s~close
  json=self~new                     -- create json instance
  return json~fromJson(jsonText)    -- use it to create ooRexx representation


/** Utility class method to ease creating minimized JSON files from an ooRexx object.
*   If a legible (with ignorable whitespace to ease reading for humans) JSON file
*   is desired instead, supply <code>.true</code> as the third argument.
*
<p>Example:
<pre>
   .json~toJsonFile('some.json',someRexxObject)
</pre>
*
*  @param fn file name or file object to write produced JSON text to
*  @param rexxObject rexxObject to encode as JSON
*  @param isLegible optional logical value, defaults to <code>.false</code> (create minimized JSON string)
*
*/
::method toJsonFile class
  use strict arg fn, rexxObject, legible=.false -- TODO: adjust test and documentation! 202302-06
  if arg()=3 then       -- check supplied argument
     .Validate~logical("legible",legible) -- test for logical value 0 or 1

  json=self~new                  -- create json instance
  jsonText=json~toJSON(rexxObject, legible)  -- create JSON text representing the rexxObject

   -- open stream for writing, delete existing file, if any, write json text to it
  .stream~new(fn)~~open("write replace")~~charout(jsonText)~~close

::method toJSON class unguarded     -- make available via class object
  j=self~new                        -- create JSON instance
  forward to (j)                    -- forward message to JSON instance

::method fromJSON class unguarded   -- make available via class object
  j=self~new                        -- create JSON instance
  forward to (j)                    -- forward message to JSON instance


/**
 * Converts a Rexx object to JSON formatting
 *
 * @param  rexxObject   The object to convert.  Accepts MapCollection, OrderedCollections,
 *                      or String objects, JsonBoolean objects or nil.  Otherwise, it calls
 *                      the makeArray method for the object, returning
 *                      "null" if no makeArray method is available.
 */
::method toJSON
    expose buffer
    use strict arg rexxObject, legible=(.false) -- default to minimized JSON

    if arg()=2 then       -- check supplied argument
       .Validate~logical("legible",legible) -- test for logical value 0 or 1

    buffer = .mutablebuffer~new()
    if legible then  -- human readable (adds ignorable whitespace)
       self~parseRexxObjectLegible(rexxObject)
    else             -- default: minimized version (no ignorable whitespace)
       self~parseRexxObject(rexxObject)

    return buffer~string

::method parseRexxObject private
    expose buffer
    use strict arg rexxObject

    select
        when rexxObject~isA(.string) then do
           call string2json buffer, rexxObject
        end
        when rexxObject~isA(.OrderedCollection) then do
            self~parseRexxArray(rexxObject)
        end
        when rexxObject~isA(.MapCollection) then do
            self~parseRexxDirectory(rexxObject)
        end
        when rexxObject~isNil then do
            buffer~append('null')
        end
        when rexxObject~hasMethod('makejson') then do    -- object can render itself as a JSON string; handles .JsonBoolean as well
           buffer~append(rexxObject~makeJson)
        end
        when rexxObject~hasMethod('makearray') then do
            self~parseRexxObject(rexxObject~makearray())
        end
        when rexxObject~hasMethod('makestring') then do  -- object can render itself as string
           buffer~append('"', rexxObject~makeString~changeStr('"','\"'), '"')
        end
        otherwise
            -- buffer~append('null') -- original implementation eradicated any information
            -- run the object's string method and use that value which may help debugging, if needed
            buffer~append('"', rexxObject~string~changeStr('"','\"'), '"')
    end

::method parseRexxArray private
    expose buffer
    use strict arg rexxObject

    buffer~append('[')
    if rexxObject~items() == 0 then buffer~append(']')
    else do
        do item over rexxObject
            self~parseRexxObject(item)
            buffer~append(',')
        end
        buffer~overlay(']', buffer~length)
    end

::method parseRexxDirectory private
    expose buffer
    use strict arg rexxObject

    buffer~append('{')
    if rexxObject~items == 0 then buffer~append('}')
    else do
        do index over rexxObject~allindexes~sort -- index in JSON must be a quoted string
            if index~isA(.string) then self~parseRexxString(index, .true)
            else buffer~append('"'index~class'"')
            buffer~append(':')
            self~parseRexxObject(rexxObject[index])
            buffer~append(',')
        end
        buffer~overlay('}', buffer~length)
    end

::method parseRexxString private
    expose buffer eJS
    use strict arg rexxObject, quoted = .false

    if rexxObject~length == 0 then buffer~append('""')
    else do
        if rexxObject~dataType('n') then do
            if quoted then buffer~append('"'rexxObject'"')
            else buffer~append(rexxObject)
        end
        else do
            buffer~append('"')
            do i = 1 to rexxObject~length
                char = rexxObject~substr(i, 1)
                if char = "\", rexxObject~match(i + 1, "u"), rexxObject~substr(i + 2, 4)~dataType("x") then do
                    -- balanced with our special handling of \uXXXX escape
                    -- sequences (keep as-is), we also keep them as-is here
                    buffer~append(rexxObject~substr(i, 6))
                    i += 5
                end
                else if eJS~hasIndex(char) then buffer~append(eJS[char])
                else if char <= '1f'x then buffer~append("\u00" || char~c2x)
                else buffer~append(char)
            end
            buffer~append('"')
        end
    end


/* ========================================================================== */
/* legible (human oriented) versions of creating JSON string: use whitespace
   to make structure and name(key)/value pairs easier to read for humans
*/

::method parseRexxObjectLegible private
    expose buffer leadin
    use strict arg rexxObject, level=0, leadin="   "
    select
        when rexxObject~isA(.string) then do
           call string2json buffer, rexxObject
        end
        when rexxObject~isA(.OrderedCollection) then do
            self~parseRexxOrderedCollectionLegible(rexxObject,level)
        end
        when rexxObject~isA(.MapCollection) then do
            self~parseRexxMapCollectionLegible(rexxObject,level)
        end
        when rexxObject~isNil then do
            buffer~append('null')
        end
        when rexxObject~hasMethod('makejson') then do    -- object can render itself as a JSON string; handles .JsonBoolean as well
           buffer~append(rexxObject~makeJson)
        end
        when rexxObject~hasMethod('makearray') then do
            self~parseRexxObjectLegible(rexxObject~makearray, level+1)
        end
        when rexxObject~hasMethod('makestring') then do  -- object can render itself as string
           buffer~append('"', rexxObject~makeString~changeStr('"','\"'), '"')
        end
        otherwise
            -- buffer~append('null') -- original implementation eradicated any information
            -- run the object's string method and use that value which may help debugging, if needed
            buffer~append('"', rexxObject~string~changeStr('"','\"'), '"')
    end

::method parseRexxOrderedCollectionLegible private
    expose buffer leadin crlf
    use strict arg rexxObject, level

    buffer~append('[')
    level+=1   -- indent
    items=rexxObject~items
    if items == 0 then buffer~append(']')
    else do
        buffer~append(crlf, leadin~copies(level))
        do counter c item over rexxObject
            self~parseRexxObjectLegible(item, level)
            if c<>items then buffer~append(',', crlf, leadin~copies(level))
                        else buffer~append(     crlf, leadin~copies(level-1), ']')
        end
    end
    level-=1   -- outdent

::method parseRexxMapCollectionLegible private
    expose buffer leadin crlf
    use strict arg rexxObject, level

    buffer~append('{')
    level+=1   -- indent
    items=rexxObject~items
    if items == 0 then buffer~append('}')
    else do
        buffer~append(crlf, leadin~copies(level))
        do counter c index over rexxObject~allIndexes~sort -- index in JSON must be a quoted string
            if index~isA(.string) then self~parseRexxStringLegible(index, .true, level)
            else buffer~append('"'index~class'"')     -- TODO: just leave "index" there?
            buffer~append(': ')
            self~parseRexxObjectLegible(rexxObject[index], level)
            if c<>items then buffer~append(',', crlf, leadin~copies(level) )
                        else buffer~append(     crlf, leadin~copies(level-1), '}')
        end
    end
    level-=1   -- outdent

::method parseRexxStringLegible private
    expose buffer leadin eJS crlf
    use strict arg rexxObject, quoted = .false, level

    if rexxObject~length == 0 then buffer~append('""')
    else do
        if rexxObject~dataType('n') then do
            if quoted then buffer~append('"'rexxObject'"')
            else buffer~append(rexxObject)
        end
        else do
            buffer~append('"')
            do i = 1 to rexxObject~length
                char = rexxObject~substr(i, 1)
                if char = "\", rexxObject~match(i + 1, "u"), rexxObject~substr(i + 2, 4)~dataType("x") then do
                    -- balanced with our special handling of \uXXXX escape
                    -- sequences (keep as-is), we also keep them as-is here
                    buffer~append(rexxObject~substr(i, 6))
                    i += 5
                end
                else if eJS~hasIndex(char) then buffer~append(eJS[char])
                else if char <= '1f'x then buffer~append("\u00" || char~c2x)
                else buffer~append(char)
            end
            buffer~append('"')
        end
    end
/* ========================================================================== */

/**
 * Recursively converts a JSON text to Rexx objects.
 *
 * @param  jsonString   A JSON text.
 */
::method fromJSON
    expose jsonString jsonPos jsonStringLength
    signal on user parseError
    use strict arg jsonString

    jsonPos = 1
    jsonStringLength = jsonString~length
    self~trimLeadingWhitespace()
    rexxObject = self~parseJSONvalue()
    if jsonPos > jsonStringLength then return rexxObject
    else do
        self~trimLeadingWhitespace()
        if jsonPos > jsonStringLength then return rexxObject
        message = 'Expected end of input'
        signal extraChars
    end
return .nil

parseError:
    c = condition('o')
    message = c['ADDITIONAL'][1]
extraChars:
    raise syntax 93.900 array(message 'at' jsonString~substr(jsonPos, 25)~strip("t"))
return .nil

/**
 * Determines type of value.
 *
 */
::method parseJSONvalue private
    expose jsonString jsonPos
    signal on user parseError

    parse value jsonString with =(jsonPos) char +1
    select
        when char == '{' then do
            jsonPos = jsonPos + 1
            return self~parseJSONobject()
        end
        when char == '[' then do
            jsonPos = jsonPos + 1
            return self~parseJSONarray()
        end
        when char == '"' then do
            jsonPos = jsonPos + 1
            return self~parseJSONstring()
            -- return self~parseJSONstring()
        end
        otherwise return self~parseJSONother()
    end
return
parseError: raise propagate

/**
 * Converts a JSON object into a Rexx directory object.
 *
 */
::method parseJSONobject private
    expose jsonString jsonPos
    signal on user parseError

    rexxDirectory = .directory~new()

    self~trimLeadingWhitespace()
    parse value jsonString with =(jsonPos) char +1
    if char == '}' then do
        jsonPos = jsonPos + 1
        return rexxDirectory
    end
    else self~parseJSONobjectValue(rexxDirectory)

    do forever
        self~trimLeadingWhitespace()
        parse value jsonString with =(jsonPos) char +1
        select
            when char == '}' then do
                jsonPos = jsonPos + 1
                return rexxDirectory
            end
            when char == ',' then do
                jsonPos = jsonPos + 1
                self~parseJSONobjectValue(rexxDirectory)
            end
            otherwise raise user parseError array('Expected end of an object or new value')
        end
    end
return
parseError: raise propagate

/**
 * Converts JSON name:value pairs into a Rexx directory item@index.
 *
 * @param  rexxDirectory   A Rexx directory object.
 */
::method parseJSONobjectValue private
    expose jsonString jsonPos
    signal on user parseError
    use strict arg rexxDirectory

    self~trimLeadingWhitespace()
    parse value jsonString with =(jsonPos) char +1
    if char == '"' then do
        jsonPos = jsonPos + 1
        index = self~parseJSONstring()
    end
    else raise user parseError array('Name must be a quoted string')

    self~trimLeadingWhitespace()
    parse value jsonString with =(jsonPos) char +1
    if char == ':' then do
        jsonPos = jsonPos + 1
        self~trimLeadingWhitespace()
        rexxDirectory[index] = self~parseJSONvalue()
    end
    else raise user parseError array('Expected colon separating object name and value')
return
parseError: raise propagate

/**
 * Converts a JSON array into a Rexx array object.
 *
 */
::method parseJSONarray private
    expose jsonString jsonPos
    signal on user parseError

    rexxArray = .array~new()

    self~trimLeadingWhitespace()
    parse value jsonString with =(jsonPos) char +1
    if char == ']' then do
        jsonPos = jsonPos + 1
        return rexxArray
    end
    else self~parseJSONarrayValue(rexxArray)

    do forever
        self~trimLeadingWhitespace()
        parse value jsonString with =(jsonPos) char +1
        select
            when char == ']' then do
                jsonPos = jsonPos + 1
                return rexxArray
            end
            when char == ',' then do
                jsonPos = jsonPos + 1
                self~parseJSONarrayValue(rexxArray)
            end
            otherwise raise user parseError array('Expected end of an array or new value')
        end
    end
return
parseError: raise propagate

/**
 * Converts a JSON array values into Rexx array items.
 *
 * @param  rexxArray   A Rexx array object.
 */
::method parseJSONarrayValue private
    expose jsonString
    signal on user parseError
    use strict arg rexxArray

    self~trimLeadingWhitespace()
    index = rexxArray~last
    if .nil == index then index = 0
    rexxArray[index + 1] = self~parseJSONvalue()
return
parseError: raise propagate

/**
 * Converts a quoted JSON string into a Rexx string object.
 *
 */
::method parseJSONstring private
    expose jsonString uJS jsonPos jsonStringLength
    signal on user parseError

    rexxString = .mutablebuffer~new()
    do forever
        parse value jsonString with =(jsonPos) char +1
        if char == '\' then do
            parse value jsonString with =(jsonPos) char2 +2
            if uJS~hasIndex(char2) then do
                -- two-character escape sequences \" \\ \/ \b \f \n \r \t
                jsonPos = jsonPos + 2
                rexxString~append(uJS[char2])
            end
            else if jsonString~match(jsonPos, "\u00") then do
                -- \u00XX escape sequence is supported
                hex = jsonString[jsonPos + 4, 2]
                if hex~length = 2, hex~dataType("x") then do
                    jsonPos = jsonPos + 6
                    rexxString~append(hex~x2c)
                end
                else
                    raise user parseError array("Invalid escape sequence")
            end
            else if jsonString~match(jsonPos, "\u") then do
                -- in general \uXXXX escape sequences are not supported
                -- as ooRexx has no Unicode support
                -- short of failing, we just just keep any \uXXXX as-is
                hex = jsonString[jsonPos + 2, 4]
                if hex~length = 4, hex~dataType("x") then do
                    jsonPos = jsonPos + 6
                    rexxString~append("\u", hex)
                end
                else
                    raise user parseError array("Invalid escape sequence")
            end
            else do
                raise user parseError array("Invalid escape sequence")
            end
        end
        else if char == '"' then do
            jsonPos = jsonPos + 1
            return .jsonString~new(rexxString~string)
            -- return rexxString~string
        end
        else do
            -- append to the string up to the next quote or backslash
            stop = jsonString~verify('\"', "match", jsonPos)
            if stop == 0
                then stop = jsonStringLength + 1
            rexxString~append(jsonString[jsonPos, stop - jsonPos])
            jsonPos = stop
        end
        if jsonPos > jsonStringLength then raise user parseError array('Expected end of a quoted string')
    end
return
parseError: raise propagate

/**
 * Converts other JSON types into Rexx objects.
 *
 */
::method parseJSONother private
    expose jsonString ctrl jsonPos jsonStringLength
    signal on user parseError

    length = jsonStringLength + 1
    do i = jsonPos while i \== length
        parse value jsonString with =(i) char +1
        if ctrl~hasIndex(char) then leave
    end
    parse value jsonString with =(jsonPos) string +(i - jsonPos)
    if string~datatype('n') then do
        jsonPos = jsonPos + string~length
        return string
    end
    else do
        select
            when string == 'false' then do
                jsonPos = jsonPos + string~length
                return .JsonBoolean~false
                -- return .false
            end
            when string == 'true' then do
                jsonPos = jsonPos + string~length
                return .JsonBoolean~true
                -- return .true
            end
            when string == 'null' then do
                jsonPos = jsonPos + string~length
                return .nil
            end
            otherwise nop
        end
    end
raise user parseError array('Invalid JSON value')
return
parseError: raise propagate

/**
 * Skips allowed whitespace between values.
 *
 */
::method trimLeadingWhitespace private
    expose jsonString jsonPos jsonStringLength
    jsonPos = jsonString~verify('20 09 0d 0a'x, , jsonPos)
    if jsonPos == 0
        then jsonPos = jsonStringLength + 1


/* ========================================================================= */

/** An ooRexx class to represent a JSON boolean (logical) value. It inherits from
    the ooRexx mixinclass <code>Comparable</code> and therefore implements its abstract
    method <code>compareTo</code>.
    To get access to the <code>JsonBoolean</code> <code>true</code> and <code>false</code>
    sentinels it is advised to use the JSON class attributes
    <code>true</code> and <code>false</code>.
*/
::class "JSONBoolean" public inherit comparable

/** Class getter attribute method that refers to the proxy object that represents the value <code>.true</code>.
*/
::attribute true  get class  unguarded -- true proxy, class getter method

/** Class getter attribute method that refers to the proxy object that represents the value <code>.false</code>.
*/
::attribute false get class  unguarded -- false proxy, class getter method


/** Make sure that only the JsonBoolean class can create instances.
*/
::method new class private
  forward class (super)

/** Finalizes the class initialization by creating the two proxy class attribute values <code>true</code>
*   and <code>false</code>.
*/
::method    activate  class   -- initialization of class object complete, we now can use everything
  expose true false
  true =self~new(.true)       -- create and store true proxy value
  false=self~new(.false)      -- create and store false proxy value

/** Constructor that saves argument in its attribute 'value'.
* @param value mandatory Rexx string representing the logical value
*/
::method init           -- make constructor method inaccessible to other classes, own metaclass is allowed to access directly
  expose value
  use strict arg value  -- assign boolean value

/** Forwards message to the string value of the JBoolean.
*/
::method unknown unguarded
  expose value          -- the string value of this JsonBoolean
  use arg msg, args
  forward to (value) message (msg) arguments (args)  -- maybe a method of the string value, forward it


/** Equal comparison method, needs to be overriden otherwise .Object's method gets run instead.
* @param other the other object representing a Boolean/logical value
* @return <code>.true</code>, if this object and <code>other</code> can be reqarded to be equal, <code>.false</code> else
*/
::method "=" unguarded           -- equal method
  expose value
  use strict arg other  -- other must be a Boolean value
  return value~compareTo(other)=0

/** Unequal comparison method.
* @param other the other object representing a Boolean/logical value
* @return <code>.true</code>, if this object and <code>other</code> cannot be reqarded to be equal, <code>.false</code> else
*/
::method "\=" unguarded          -- unequal method
  expose value
  use strict arg other  -- other must be a Boolean value
  return value~compareTo(other)\=0

/** Unequal comparison method, forwarding to method <code>&quot;\=&quot;</code>.
*/
::method "<>" unguarded          -- synonym for "\="
  forward message ("\=")

/** Unequal comparison method, forwarding to method <code>&quot;\=&quot;</code>.
*/
::method "><" unguarded          -- synonym for "\="
  forward message ("\=")

/** Implements the abstract method inherited from the mixinclass <code>Comparable</code>.
*/
::method compareTo unguarded  -- implementation for .orderable class: must return -1 if other greater, 0 if same, 1 otherwise
  expose value
  use strict arg other  -- other must be a Boolean value

  if other~isA(.JsonBoolean) then  -- get Rexx string representing logical value
     otherValue=other~value
  else
     otherValue=other~request("string") -- request the string value

  if otherValue=.nil then
     raise syntax 88.900 array ("Argument ""other"" ["other"] has no 'MAKESTRING' method")

  if value < otherValue then return -1    -- self smaller than other
  if value = otherValue then return  0    -- self equal to other
  return                             1    -- self greater than other

/** Renders the object as a Rexx string representing its logical value.
* @return a Rexx string representing its logical value, either &quot;<code>0</code>&quot; or
*         &quot;<code>1</code>&quot;
*/
::method makeString unguarded -- allow instances of this class to be a plug in replacement for Rexx logical values
  expose value
  return value

/** Renders the object as a JSON string representing its logical value.
* @return a string representing its logical value JSON encoded, either &quot;<code>false</code>&quot;
*         or &quot;<code>true</code>&quot;
*/
::method makeJSON unguarded   -- creates the string "true" or "false", depending on the attribute "value"
  expose value
  if value=.true then return "true"
  return "false"


/* ========================================================================= */

/** An ooRexx class that represents a JSON string value. To force numeric values
*   to be encoded as Json strings, use this class, e.g.
*   jstring=.JsonString~new("123456789")
*/
::class "JsonString" subclass string  public
::method makeJSON unguarded
  buffer=.MutableBuffer~new
  call string2json buffer, self
  return buffer~string


/* ========================================================================= */
/** A routine that creates a Json encoding for a string. In case the string is an
*   instance of the JsonString class, and the value is numeric, then it gets
*   encoded as a quoted Json string. If the value is numeric and the instance is
*   an instance of the String class, then the value gets encoded as a Json number
*   (no quotes).
*/
::routine string2json
   use arg buffer, rexxObject

   if rexxObject~dataType('n') then
   do
      if rexxObject~isA(.jsonString) then buffer~append('"',rexxObject,'"')   -- as a string (quoted)
                                     else buffer~append(rexxObject)           -- as a number
   end
   else
   do
      eJS=.js.eJS    -- save environment lookups from now on
      buffer~append('"')
      do i = 1 to rexxObject~length
         char = rexxObject~substr(i, 1)
         if char = "\", rexxObject~match(i + 1, "u"), rexxObject~substr(i + 2, 4)~dataType("x") then do
            -- balanced with our special handling of \uXXXX escape
            -- sequences (keep as-is), we also keep them as-is here
            buffer~append(rexxObject~substr(i, 6))
            i += 5
         end
         else if eJS~hasIndex(char) then buffer~append(eJS[char])
         else if char <= '1f'x      then buffer~append("\u00", char~c2x)
         else                            buffer~append(char)
      end
      buffer~append('"')
   end
   return

call create_files
say "---"

-- do fn over "test_01.json", "test_01.json", "test_02.json", "test_02.json"
do fn over "test_02.json", "test_02.json", "test_01.json", "test_01.json"
   res=read_file(fn)
   say fn":" "res~items:" res~items
end


::requires 'json.cls'

/* --
[
{"Item 1":"Value 1"},
{"Item 2":"Value 2"},
{"Item 3":"Value 3"},
{"Item 4":"Value 4"},
{"Item 5":"Value 5"},
{"Item 6":"Value 6"},
{"Item 7":"Value 7"},
{"Item 8":"Value 8"},
{"Item 9":"Value 9"},
{"Item 10":"Value 10"}
]
-- */

::routine create_files

dt=.dateTime~new
nl="0d0a"x

mb1=.mutableBuffer~new
mb1~append('[',nl)

do i=1 to 90000
   mb1~append('{"Item ', i, '":"Value ', i, '"}')
   if i<90000 then mb1~append(',',nl)
end

mb2=mb1~copy~~append(',',nl) -- copy current mb
mb1~append(nl,']')

fn="test_01.json"
say "created" fn":" dt~elapsed
dt1=.dateTime~new
s=.stream~new(fn)~~open("replace write")
s~~charout(mb1~string)~~close
say "wrote" fn":" dt1~elapsed

do i=90001 to 180000
   mb2~append('{"Item ', i, '":"Value ', i, '"}')
   if i<180000 then mb2~append(',',nl)
end
mb2~append(nl,']')

fn="test_02.json"
say "created" fn":" dt~elapsed
dt2=.dateTime~new
s=.stream~new(fn)~~open("replace write")
s~~charout(mb2~string)~~close
say "wrote" fn":" dt2~elapsed


::routine read_file
  use arg fn
  dt=.dateTime~new
  say "importing" fn "..."
  o=.json~fromJsonFile(fn)
  say "importing" fn "lasted:" dt~elapsed
  return o

_______________________________________________
Oorexx-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/oorexx-devel

Reply via email to