**************** New JSON Extender (Experimental) * Part Trois***********

Started by td, October 12, 2021, 02:48:45 PM

Previous topic - Next topic

stanl

Quote from: td on October 29, 2021, 09:42:17 AM
Quote from: stanl on October 29, 2021, 09:17:21 AM


No offense, but I thought rgba was a key, i..e jsKeyExist().... and the foreach loop is taken directly from the help file for jsValueGet(). I was thinking the Extender could pull back the map value, i.e. key:value if jsKeyExists() returned @TRUE.... Point is: If keyexistat => return key value(s).... so maybe a SearchKey() function is worth asking about.

Rather induce more typos... a quote from the help file:

"This function retrieves the value of a key/value pair contained within the passed in JSON object, one of its child objects, in a JSON array or a JSON array element contained within the container or sub container. If the key's value is a JSON object (@JsonObj), the returned value will be a JSON extender object handle. If the returned key's value is a JSON array (@JsonArr), and a bracketed array index is not a postfix to the key name, the return value is a container-handle representing the array."

Also for the JSON path discussion in the help file:

"Important: JSON objects and arrays are collectively referred to as containers in the extender's documentation."


So.. if jsKeyExist() there is no way to immediately return the key value(s)...  This is not an immediate issue as I am working with a Json extract with defined keys... but for testing with any nuances of the data I receive, I'll stick with a PS alternative.


[EDIT}


$json = '{ "color": "red", "rgba": "255,0,0,1"}'
$value = ($json | ConvertFrom-Json).rgba
$value

td

Not sure what you mean by "if jsKeyExist() there is no way to immediately return the key value(s)...". The purpose of the subroutine isn't to return values.

If you are referring to jsValueGet, you can return any value you want, including arrays and objects. 
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

JTaylor

Thank you.   Since you knew what I was doing and offered a solution I just assumed it should work so was trying to figure out what I might be doing wrong.   I figured the problem was what it turned out to be but wanted to make sure.    This would be helpful but, again, what would be REALLY helpful is the default value option for jsValueGet() and adding a jsKeyExist() function so we all don't have to maintain a subroutine for something I would think most everyone would find very useful would be great :)

Yes. I know I can write a subroutine and already have and plan to post it but can't use it until this other is resolved anyway.  Guess I could if I wanted to do it via error trapping but if you are working on it I will wait.

Thanks again.

Jim

stanl

Quote from: td on October 29, 2021, 01:22:39 PM
Not sure what you mean by "if jsKeyExist() there is no way to immediately return the key value(s)...". The purpose of the subroutine isn't to return values.

If you are referring to jsValueGet, you can return any value you want, including arrays and objects.


Understood. The Json I work with is more or less database records, so I use "parent" and "elements"  where the parent can be iterated to it's keys and each key can return one or more elements which are values, skipping a "container" type, i.e. object or array when applicable. Of course, I need to fit my way of thinking to the Extender rather than the other way around.  ;D


.... but for chuckles


$json = '{ "color": "red", "rgba": [255,0,0,1]}'
$keys = ($json | ConvertFrom-Json).psobject.properties.name
$key = "rgba"
if ($keys -match $key) {
   Write-Host $key" exists"
   Write-Host ($json | ConvertFrom-Json).$key
   }
else {
   Write-Host $key" does not exists" }


#should return
#rgba exists
#255 0 0 1


($json | ConvertFrom-Json)|get-member


#should return
#  TypeName: System.Management.Automation.PSCustomObject


#Name        MemberType   Definition                   
#----        ----------   ----------                   
#Equals      Method       bool Equals(System.Object obj)
#GetHashCode Method       int GetHashCode()             
#GetType     Method       type GetType()               
#ToString    Method       string ToString()             
#color       NoteProperty string color=red             
#rgba        NoteProperty Object[] rgba=System.Object[]  <= consistent with Extender



td

Just for fun and not trying to prove anything but here is a WinBatch somewhat equivalent script. It is an oversimplification but it's Saturday and I have an outdoor project to get to because the sun is actually shining today. 

Code (winbatch) Select
AddExtender('ilcjs44i.dll', 0, 'ilcjs64i.dll')

values = ''
json = '{ "color": "red", "rgba": [255,0,0,1]}'
jshandle = jsParse(json)
key = 'rgba'
path = jsKeyPaths(jshandle, key)
if !!ArrInfo(path, 1) 
   jsArray = jsValueGet(jshandle, path[0])
   paths = jsKeyPaths(jsArray, '')
   foreach elem in paths
      values := jsValueGet(jsArray, elem):','
   next
endif

if values == '' then text = 'Does not exit'
else text = 'Exists':@lf: values

message (key, text)
exit
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

yes.. somewhat equivalent is an operative term.... My main point was going directly from if key exists to element value [not its quasi-datatype]. I never said the extender was incapable of that, just more hoops than I cared to jump.

td

Not sure that I would agree with the "extra hoops" statement as it is more or less a trivial task. But that is just my subjective opinion.  The extender was originally written to return delimited lists of elements but that presents some issues with regard to nested arrays and embedded delimiters. It would be nice to return a WIL array but WIL arrays cannot have arrays as elements which leads to an inconsistent approach to arrays. I suppose it would be possible to add a function to the extender that returns JSON arrays as delimited lists.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

Quote from: td on October 30, 2021, 04:56:57 PM
Not sure that I would agree with the "extra hoops" statement as it is more or less a trivial task. But that is just my subjective opinion.  The extender was originally written to return delimited lists of elements but that presents some issues with regard to nested arrays and embedded delimiters. It would be nice to return a WIL array but WIL arrays cannot have arrays as elements which leads to an inconsistent approach to arrays. I suppose it would be possible to add a function to the extender that returns JSON arrays as delimited lists.


Again agreed. To be clear, I already have a working compiled script with the previous release of the Extender. It works against a known Json structure from an API query. It is fast and moves the Json into Excel.  My questions here are moot in terms of my actual need... more just exploring other Json returns for Extender possibilities. It is unfair to set up what looks like a PS<>WB competition. In PS everything is an Object, so the dot notation ($json | ConvertFrom-Json).$key once understood is pretty intuitive.


But in your example - if your jskeyexist() for "color" returns "red", but for "rgba" returns a nested array which has to be processed with a subsequent call to jskeypaths().  Maybe a dumb question - but what if jskeyexist() would still return a 0 that the key didn't exist but return the jsvaluetype() if it did?


[EDIT]
Code (WINBATCH) Select


#definesubroutine jsKeyExist(_handle,_Key)
   retval = Arrinfo(jsKeyPaths(_handle, _key), 1)
   If retval > 0 Then retval = jsValueType(_handle, _key)
   return retval
#endsubroutine

td

The idea has merit because knowing that a data item exists and its type is connected from the usage perspective.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

Quote from: td on October 31, 2021, 08:36:19 AM
The idea has merit because knowing that a data item exists and its type is connected from the usage perspective.


Here is a shot:

       
  • Json taken from the Extender documentation for jsValueType()
  • added one entry -  "version":: "1.0",
  • Added error handler in jsKeyExists
  • tested with several 'keys'
Not sure why some test keys error as they are valid keys.
Code (WINBATCH) Select


IntControl(73,1,0,0,0)
Gosub udfs
AddExtender('ilcjs44i.dll', 0, 'ilcjs64i.dll')
JsonData = $"{
    "version":: "1.0",
    "dependencies":: {
      "Microsoft.NETCore.UniversalWindowsPlatform":: "5.2.2",
      "Microsoft.Toolkit.Uwp.Notifications":: "1.1.0",
      "QueryString.NET":: "1.0.0"
    },
    "frameworks":: {
      "uap10.0":: {}
    },
    "runtimes":: {
      "win10-arm":: {},
      "win10-arm-aot":: {},
      "win10-x86":: {},
      "win10-x86-aot":: {},
      "win10-x64":: {},
      "win10-x64-aot":: {}
    }
  }$"


If ! jsValid(JsonData) Then Terminate(@TRUE,"Cannot Continue","Invalid Json")
jsHandle = jsParse(JsonData)
;; This can still error if the key contains path information and
;; it is syntactically malformed.
;try these
;cKey = "version"   ;should return 1.0
;cKey = "runtimes"  ;should return Json Object
;cKey = "QueryString.NET" ;should error
cKey = "uap10.0"   ;should error
result = jsKeyExist(jsHandle,cKey)
If result == 0 Then Message(cKey,"Does Not Exist")
If result >1 && result <=8
   Message(cKey,jsValueGet(jsHandle,cKey))
Endif
If result == 16
   path = jsKeyPaths(jsHandle,cKey)
   values = ''
   jsArray = jsValueGet(jshandle, path[0])
   paths = jsKeyPaths(jsArray, '')
   foreach elem in paths
      values := jsValueGet(jsArray, elem):','
   next
   Message(cKey,values)
Endif
If result == 32 Then Message(cKey,"is a Json Object")
jsConClose(jsHandle)
exit


:udfs
#definesubroutine jsKeyExist(_handle,_Key)
   IntControl(73,1,0,0,0)
   retval = Arrinfo(jsKeyPaths(_handle, _key), 1)
   If retval > 0 Then retval = jsValueType(_handle, _key)
   return retval
   :WBERRORHANDLER
   geterror()
   jsConClose(jsHandle)
   Terminate(@TRUE,"Error Encountered",errmsg)
Exit
#endsubroutine


#DefineSubRoutine geterror()
   wberroradditionalinfo = wberrorarray[6]
   lasterr = wberrorarray[0]
   handlerline = wberrorarray[1]
   textstring = wberrorarray[5]
   linenumber = wberrorarray[8]
   errmsg = "Error: ":lasterr:@LF:textstring:@LF:"Line (":linenumber:")":@LF:wberroradditionalinfo
   Return(errmsg)
#EndSubRoutine
Return


td

You get errors because you don't have square brackets around the keys with embedded key delimiters (periods) in their names.

From the help file, "Each key name can be surrounded by square brackets ( [key-name]) to avoid conflicts between JSON names and name delimiter".
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

Quote from: td on November 01, 2021, 08:02:27 AM
You get errors because you don't have square brackets around the keys with embedded key delimiters (periods) in their names.

From the help file, "Each key name can be surrounded by square brackets ( [key-name]) to avoid conflicts between JSON names and name delimiter".


Yes, then what would the point be with a function to see if a key exists... If you have to surround key with brackets and . comment after that.. there is no purpose for a key search, you are probably sure it already exists, or just do a text search.


Point is I cannot search for "QueryString.NET" and return "1.0.0" without prior knowledge of brackets and . separators.

td

If you are searching for "key" then make it "[key]". You don't need to know anything special. Presumably, you have read the extender documentation before you use it so you already know what the default key delimiter is, a period. You have three choices: enclose all key names in brackets, enclose key names in brackets only when you detect a delimiter in the key name or change the delimiter used by the extender's functions.  With any choice, the point of the function is to return paths that allow you to obtain the value and data types of any and all instances of the key.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

So what is the correct search key for [QueryString.NET] or does the jskeyexist() need tweaking?

td

The function does not need "tweaking" because brackets are sometimes required.  It is changing a bit in the next release but brackets will still be needed brackets when the key name contains a key delimiter.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

Quote from: td on November 02, 2021, 07:38:05 AM
The function does not need "tweaking" because brackets are sometimes required.  It is changing a bit in the next release but brackets will still be needed brackets when the key name contains a key delimiter.


Don't need to get too Existential about this... but you send a string value to a function and it either exists or it doesn't, and if it does the reply from the function indicates how to deal with it.

td

You pass in a nonexistent key your get an empty array. If you want to know why read the documentation. The function is designed to minimize errors although there are a few that have to be reported because of system-related issues.  The next release would break existing scripts without the bracket requirement. We do plan ahead...
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

Code (WINBATCH) Select


IntControl(73,1,0,0,0)
Gosub udfs
AddExtender('ilcjs44i.dll', 0, 'ilcjs64i.dll')
JsonData = $"{
    "version":: "1.0",
    "dependencies":: {
      "Microsoft.NETCore.UniversalWindowsPlatform":: "5.2.2",
      "Microsoft.Toolkit.Uwp.Notifications":: "1.1.0",
      "QueryString.NET":: "1.0.0"
    },
    "frameworks":: {
      "uap10.0":: {}
    },
    "runtimes":: {
      "win10-arm":: {},
      "win10-arm-aot":: {},
      "win10-x86":: {},
      "win10-x86-aot":: {},
      "win10-x64":: {},
      "win10-x64-aot":: {}
    }
  }$"


If ! jsValid(JsonData) Then Terminate(@TRUE,"Cannot Continue","Invalid Json")
jsHandle = jsParse(JsonData)
;; This can still error if the key contains path information and
;; it is syntactically malformed.
;try these
;cKey = "version"   ;should return 1.0
;cKey = "runtimes"  ;should return Json Object
;cKey = "QueryString.NET"
cKey = "uap10.0"   
result = jsKeyExist(jsHandle,cKey)                 
If result == 0 Then Message(cKey,"Does Not Exist")
If result >1 && result <=8
   Message(cKey,jsValueGet(jsHandle,cKey))
Endif
If result == 16
   path = jsKeyPaths(jsHandle,cKey)
   values = ''
   jsArray = jsValueGet(jshandle, path[0])
   paths = jsKeyPaths(jsArray, '')
   foreach elem in paths
      values := jsValueGet(jsArray, elem):','
   next
   Message(cKey,values)
Endif
If result == 32 Then Message(cKey,"is a Json Object")
If result == 64 Then Message(cKey,"key exists but value cannot be determined")
jsConClose(jsHandle)
exit


:udfs
#definesubroutine jsKeyExist(_handle,_Key)
   IntControl(73,1,0,0,0)
   retval = Arrinfo(jsKeyPaths(_handle, _key), 1)
   If retval > 0 Then retval = jsValueType(_handle, _key)
   return retval
   :WBERRORHANDLER
   IntControl(73,1,0,0,0)
   return 64
Exit
#endsubroutine


#DefineSubRoutine geterror()
   wberroradditionalinfo = wberrorarray[6]
   lasterr = wberrorarray[0]
   handlerline = wberrorarray[1]
   textstring = wberrorarray[5]
   linenumber = wberrorarray[8]
   errmsg = "Error: ":lasterr:@LF:textstring:@LF:"Line (":linenumber:")":@LF:wberroradditionalinfo
   Return(errmsg)
#EndSubRoutine
Return



td

Your script contains a bug so it should error. You use the jsValueGet function incorrectly. The second parameter needs to be a JSON path, not just a key name. The help file is clear on that. The design of the extender is based on the idea of using jsKeyPath to establish a valid path to a JSON key/value pair. Once you have the path you can use the path instead of wasting processing time repeating the key name search. The jsKeyPath function is intended to not produce errors so it can be used for existential testing of key names without error suppression. JsKeyPaths may produce a few syntax-based errors because we missed one or two in called functions within the extender. The purpose of the other functions does not include existential testing so they are implemented to produce more error information.

The jsKeyPaths function does need better documentation but documentation needs to change for the next release anyway. That makes the documentation problem irrelevant at this point.

This thread has already lived too long so it is time for me to bow out.
"No one who sees a peregrine falcon fly can ever forget the beauty and thrill of that flight."
  - Dr. Tom Cade

stanl

Quote from: td on November 03, 2021, 08:31:00 AM
This thread has already lived too long so it is time for me to bow out.


Agreed. Good luck with Part Quatre. I'll stay away from any pseudo-issues. Your JsonData was a good base for exploring stuff. I poked at it in PS enough to come up with code to walk down nested Json keys/elements and obtain value(s). But, as I previously mentioned the Json I will parse out to Excel at work is well-defined and all elements are documented. So, questions like "does key exist" are theoretical: though I learned from the rabbit-hole I went down. :-[

JTaylor

Hopefully the next version will allow the jsKeyExist() function to actually work because it isn't theoretical for me :)

Jim

stanl

Quote from: JTaylor on November 07, 2021, 10:57:37 AM
Hopefully the next version will allow the jsKeyExist() function to actually work because it isn't theoretical for me :)
Jim


Probably any confusion is.... given key = "mykey' , simple element in Json

       
  • Does key exist at all in the Json
  • Key does exist, but wondering if it exists in this section of the Json.
In either case, if the key exists could the value be retuned [actual value/stringtype/numerictype/arraytype/objecttype], or is that a separate function?

JTaylor

If I do a jsValueGet() for the same path I get data.  If I use that same path in the jsKeyExist() function it tells me it doesn't exist.

Jim