Searching a JSON array

Started by Paul M., February 03, 2022, 07:30:21 PM

Previous topic - Next topic

Paul M.

I have a program that reads in a nested JSON string  like below:
{"Value":[{"DomainId":23,"DomainName":"PU","Description":"Public Utilities","WebTitle":"","ImageUrl":"","EditButtonsOn":false,"MapServiceId":1,"MobileMapCacheId":0},{"DomainId":21,"DomainName":"PW","Description":"Public Works","WebTitle":"","ImageUrl":"","EditButtonsOn":false,"MapServiceId":2,"MobileMapCacheId":0}],"Status":0,"Message":null,"ErrorMessages":[],"WarningMessages":[],"SuccessMessages":[]}

I set up a bdl record to catch this:

mycatch   RECORD
   Value DYNAMIC ARRAY of util.JSONObject ,       # util.JSONArray
   Status STRING,
   Message STRING,
   ErrorMessages DYNAMIC ARRAY OF STRING,
   WarningMessages DYNAMIC ARRAY OF STRING,
   SuccessMessages DYNAMIC ARRAY OF String
  END RECORD


In the code  I can do.

for i = 1 to mycatch.value.getlength()
  display mycatch.value.get("DomainId")," - ", mycatch.value.get("DomainName")
END FOR

but when I ask
let i = mycatch.value.search("DomainId","PU")

it returns 0

util.JSONArray does not have a search function, so I thought a dynamic array of JSONObjects would work. 
Am I missing something simple? 

I know I can substring the JSON array part of the string and push it into a dynamic array, but that seems like over kill.

Any ideas are appreciated.

Sebastien F.

Hello,

The ARRAY.search() method does not apply to util.JSONObject properties.

I would expect a runtime error if you use the search() method on a DYNAMIC ARRAY OF <class> (looks like a bug)

If your JSON data has a static structure, you can define the complete RECORD with all fields of a "Value" element, and then use the ARRAY.search() method:

Code (genero) Select
IMPORT util
 
MAIN
   DEFINE s STRING
   DEFINE mycatch RECORD
       --Value DYNAMIC ARRAY of util.JSONObject,
       Value DYNAMIC ARRAY OF RECORD
            DomainId INTEGER,
            DomainName STRING,
            Description STRING,
            WebTitle STRING,
            ImageUrl STRING,
            EditButtonsOn BOOLEAN,
            MapServiceId INTEGER,
            MobileMapCacheId INTEGER
       END RECORD,
       Status STRING,
       Message STRING,
       ErrorMessages DYNAMIC ARRAY OF STRING,
       WarningMessages DYNAMIC ARRAY OF STRING,
       SuccessMessages DYNAMIC ARRAY OF String
      END RECORD

   LET s = '{"Value":[{"DomainId":23,"DomainName":"PU","Description":"Public Utilities","WebTitle":"","ImageUrl":"","EditButtonsOn":false,"MapServiceId":1,"MobileMapCacheId":0},{"DomainId":21,"DomainName":"PW","Description":"Public Works","WebTitle":"","ImageUrl":"","EditButtonsOn":false,"MapServiceId":2,"MobileMapCacheId":0}],"Status":0,"Message":null,"ErrorMessages":[],"WarningMessages":[],"SuccessMessages":[]}'

   CALL util.JSON.parse(s, mycatch)

   DISPLAY util.JSON.format(util.JSON.stringify(mycatch))

   DISPLAY mycatch.value.search("DomainName","PU")

END MAIN



You wrote:

QuoteIn the code  I can do.

for i = 1 to mycatch.value.getlength()
  display mycatch.value.get("DomainId")," - ", mycatch.value.get("DomainName")
END FOR

This cannot work if mycatch.value is a DYNAMIC ARRAY, there is no such method like get():

Code (genero) Select
sf@toro:/tmp$ fglcomp -M search.4gl && fglrun search.42m
search.4gl:33:25:33:27:error:(-8447) function 'get' not found in type 'base.Array'.
search.4gl:33:62:33:64:error:(-8447) function 'get' not found in type 'base.Array'.


See:

https://4js.com/online_documentation/fjs-fgl-manual-html/#fgl-topics/c_fgl_Arrays_ARRAY_methods.html

I assume you have mixed up your code, trying different types util.JSONArray / util.JSONObject / DYNAMIC ARRAY OF util.JSONObject ...

Note also that util.JSONArray.get() takes an index as argument, not a property name as when using util.JSONObject.get():

https://4js.com/online_documentation/fjs-fgl-manual-html/#fgl-topics/c_fgl_ext_util_JSONArray_get.html

https://4js.com/online_documentation/fjs-fgl-manual-html/#fgl-topics/c_fgl_ext_util_JSONObject_get.html


If the JSON data structure is variable, you can define the Value member as a util.JSONArray and then write generic code to search properties in the JSON structure.

If this is the case, send a request to the support channel and we'll follow up and provide a sample.

Seb

Leo S.

You are not forced btw to have all attributes/data structures in your target record.
The util.JSON.parse function is smart and only fills the matching members.
So if you aren't interested in some details of the JSON string transmitted you can simply omit them in your target RECORD structure.
The snippet below is working too.

Code (genero) Select

IMPORT util
MAIN
  DEFINE s STRING
  DEFINE mycatch RECORD
    Value DYNAMIC ARRAY OF RECORD
      DomainId INT,
      DomainName STRING,
      Description STRING
      --...etc
    END RECORD
  END RECORD
  LET s='{"Value":[{"DomainId":23,"DomainName":"PU","Description":"Public Utilities","WebTitle":"","ImageUrl":"","EditButtonsOn":false,"MapServiceId":1,"MobileMapCacheId":0},{"DomainId":21,"DomainName":"PW","Description":"Public Works","WebTitle":"","ImageUrl":"","EditButtonsOn":false,"MapServiceId":2,"MobileMapCacheId":0}],"Status":0,"Message":null,"ErrorMessages":[],"WarningMessages":[],"SuccessMessages":[]}'
  CALL util.JSON.parse(s,mycatch)
  DISPLAY mycatch.Value.search("DomainName","PU")
END MAIN

Regards, Leo

Leo S.

Ah, and I wanted to add that you should always
try
Code (genero) Select

DISPLAY util.JSON.proposeType(s)

to ease your work.
it would have displayed
Code (genero) Select

RECORD
    Value DYNAMIC ARRAY OF RECORD
        DomainId FLOAT,
        DomainName STRING,
        Description STRING,
        WebTitle STRING,
        ImageUrl STRING,
        EditButtonsOn BOOLEAN,
        MapServiceId FLOAT,
        MobileMapCacheId FLOAT
    END RECORD,
    Status FLOAT,
    Message STRING,
    ErrorMessages DYNAMIC ARRAY OF STRING,
    WarningMessages DYNAMIC ARRAY OF STRING,
    SuccessMessages DYNAMIC ARRAY OF STRING
END RECORD

Sebastien F.

About ARRAY.search(), in fact you don't get a runtime error because you can search for object references:

Code (genero) Select
MAIN
    DEFINE c1, c2 base.Channel
    DEFINE a DYNAMIC ARRAY OF base.Channel
    LET c1 = base.Channel.create()
    LET c2 = base.Channel.create()
    LET a[1] = NULL
    LET a[2] = c1
    LET a[3] = NULL
    LET a[4] = c2
    DISPLAY a.search("", c1)
    DISPLAY a.search("", c2)
    DISPLAY a.search("", "foo")
END MAIN


Then:
Code (genero) Select
$ fglcomp ch.4gl && fglrun ch.42m
          2
          4
          0


I have some doubts about the behavior when passing a field name, thought:

Code (genero) Select
DISPLAY a.search("xxxxxx", c1)

That one could raise a runtime error IMO.