I have a JSON result I am trying to work with in AppleScript, but because the top level items are "unnamed" I can only access them by piping the item reference, which in this case is a number. As a result, I can't iterate through it, it has to be hard coded (scroll down to the last code sample to see what I mean)
For example, this is the JSON I'm looking at:
{
"1": {
"name": "Tri 1"
},
"2": {
"name": "Tri 2"
},
"3": {
"name": "Tri 3"
},
"4": {
"name": "Orb Dave"
},
"5": {
"name": "Orb Fah"
}
}
With the help of JSON Helper I get the JSON to a more usable format (for AppleScript).
{|3|:{|name|:"Tri 3"}, |1|:{|name|:"Tri 1"}, |4|:{|name|:"Orb Dave"}, |2|:{|name|:"Tri 2"}, |5|:{|name|:"Orb Fah"}}
I can then use this code to get a list of "lights" the objects in question:
set lights to (every item in theReturn) as list
repeat with n from 1 to count of lights
set light to item n of lights
log n & light
end repeat
From that, I get:
(*1, Tri 3*)
(*2, Tri 1*)
(*3, Orb Dave*)
(*4, Tri 2*)
(*5, Orb Fah*)
You may notice the result is not in the desired order. The index is the index within the list of lights. It's not the number that appears at the top of the object. If you look to the top two pre-formated areas, you'll see the items 1,2 and 3 are Tri 1, Tri 2, and Tri 3. It is correct that Tri 3 comes first, Tri 1 second, and an Orb is third.
What I need to do is find a way to be able to iterate through the JSON in any order (sorted or not) and be able to line up "1" with "Tri 1", "3" with "Tri 3" and "5" with "Orb Fah". But I can't find ANY way to interact with the returned JSON that lets me reference the third light and return it's name. The ONLY way I can seem to be able to do it is to hard code the light indexes, such that:
log |name| of |1| of theReturn
log |name| of |2| of theReturn
log |name| of |3| of theReturn
log |name| of |4| of theReturn
log |name| of |5| of theReturn
which gives me the correct light with the correct name:
(*Tri 1*)
(*Tri 2*)
(*Tri 3*)
(*Orb Dave*)
(*Orb Fah*)
I'm thinking the problem is arising because the light ID doesn't have a descriptor or sorts. That I can't change, but I need to iterate through them programatically. Hard coding them as above is not acceptable.
Any help would be appreciated
You are dealing with a list of records here, not a list of lists. Records are key/value pairs. They do not have indexes like a list. That makes it easy if you know the keys because you just ask for the one you want. And your records have records inside them so you have 2 layers of records. Therefore if you want the value of the |name| record corresponding to |3| record then ask for it as you've discovered...
set jsonRecord to {|3|:{|name|:"Tri 3"}, |1|:{|name|:"Tri 1"}, |4|:{|name|:"Orb Dave"}, |2|:{|name|:"Tri 2"}, |5|:{|name|:"Orb Fah"}}
set record3name to |name| of |3| of jsonRecord
The downside of records in applescript is that there is no command to find the record keys. Other programming languages give you the tools to find the keys (like objective-c) but applescript does not. You have to know them ahead of time and use them as I showed.
If you don't know the keys ahead of time then you can either use JSON Helper to give you the results in a different form or use a different programming language (python, ruby, etc) to extract the information from the records.
One other option you have is to just use the json text itself without using JSON Helper. For example, if you have the json as text then you can extract the information using standard applescript commands for text objects. Your json text has the information you want on the 3rd line, the 6th, 9th etc. You could use that to your advantage and do something like this...
set jsonText to "{
\"1\": {
\"name\": \"Tri 1\"
},
\"2\": {
\"name\": \"Tri 2\"
},
\"3\": {
\"name\": \"Tri 3\"
},
\"4\": {
\"name\": \"Orb Dave\"
},
\"5\": {
\"name\": \"Orb Fah\"
}
}"
set jsonList to paragraphs of jsonText
set namesList to {}
set AppleScript's text item delimiters to ": \""
repeat with i from 3 to count of jsonList by 3
set theseItems to text items of (item i of jsonList)
set end of namesList to text 1 through -2 of (item 2 of theseItems)
end repeat
set AppleScript's text item delimiters to ""
return namesList
For each index, loop through all the items in the list looking for the one whose name matches the index:
tell application "System Events"
-- Convert the JSON file to a property list using plutil.
do shell script "plutil -convert xml1 /Users/mxn/Desktop/tri.json -o /Users/mxn/Desktop/tri.plist"
-- Read in the plist
set theItems to every property list item of property list file "/Users/mxn/Desktop/tri.plist"
set theLights to {}
-- Iterate once per item in the plist.
repeat with i from 1 to count of theItems
set theName to i as text
-- Find the item whose name is the current index.
repeat with theItem in theItems
if theItem's name is theName then
-- We found it, so add it to the results.
set theValue to theItem's value
copy {i, theValue's |name|} to the end of theLights
-- Move on to the next index.
exit repeat
end if
end repeat
end repeat
return theLights
end tell
Result:
{{1, "Tri 1"}, {2, "Tri 2"}, {3, "Tri 3"}, {4, "Orb Dave"}, {5, "Orb Fah"}}
Ideally, instead of the nested loop, we’d be able to say something like this:
set theName to i as text
set theItem to (the first item in theItems whose name is theName)
But unfortunately that produces an error.
This solution also demonstrates an alternative to JSON Helper: you can convert the JSON file to a property list using the handy plutil command line tool and use System Events' built-in support for property lists.
Related
I have a parse JSON step which outputs this sort of structure
[
{
"Value": "Sample Value 1"
},
{
"Value": "Sample Value 2"
}
]
I would like to transform the following structure
[
"Sample Value 1",
"Sample Value 2"
]
Thanks in advance
You need to loop over the original array and the value of add each value property to a linear string based array.
This flows shows you the basic steps ...
Firstly, I created a new variable of type Array that stored your original data.
Next, I initialised another variable that will hold the results of the output. When initialised, it was with no value.
Finally, using a For each action, I loop over the original array and within that, there's an Append to array variable step which adds in the value of the value property for each item to the Simple Array variable. The expression in the *Value field is ...
item()?['value']
... that will retrieve the value for each item and append it accordingly.
This is the end result ...
One thing to note is, if you want the simple array to be in the same order as the original array values, you need to go to the settings on the For each step and turn concurrency on to equals 1.
I am geocoding using OpenRefine. I pulled data from OpenStreetMaps to my datasetstructure of data
I am adding a "column based on this column" for the coordinates.I want to check that the display_name contains "Rheinland-Pfalz" and if it does, I want to extract the latitude and longitude,i.e. pair.lat + ',' + pair.lon. I want to do this iteratively but I don't know how. I have tried the following:
if(display_name[0].contains("Rheinland-Pfalz"), with(value.parseJson()[0], pair, pair.lat + ',' + pair.lon),"nothing")
but I want to do this for each index [0] up to however many there are. I would appreciate if anyone could help.
Edit: Thanks for your answer b2m.
How would I extract the display_name corresponding to the coordinates that we get. I want the output to be display_name lat,lon for each match (i.e. contains "Rheinland-Pfalz", because I have a different column containing a piece of string that I want to match with the matches generated already.
For example, using b2m's code and incorporating the display_name in the output we get 2 matches:
Schaumburg, Balduinstein, Diez, Rhein-Lahn-Kreis, Rheinland-Pfalz, Deutschland 50.33948155,7.9784308849342604
Schaumburg, Horhausen, Flammersfeld, Landkreis Altenkirchen, Rheinland-Pfalz, Deutschland 52.622319,14.5865283
For each row, I have another string in a different column. Here the entry is "Rhein-Lahn-Kreis". I want to filter the two matches above to only keep those containing my string in the other column. In this case "Rhein-Lahn-Kreis" but the other column entry is different for each row. I hope this is clear and I would greatly appreciate any help
Assuming we have the following json data
[
{"display_name": "BW", "lat": 0, "lon": 1},
{"display_name": "NRW 1", "lat": 2, "long": 3},
{"display_name": "NRW 2", "lat": 4, "lon": 5}
]
You can extract the combined elements lat and long with forEach and filter using the following GREL expression e.g. in the Add column based on this column dialog.
forEach(
filter(
value.parseJson(), geodata, geodata.display_name.contains("NRW")
), el, el.lat + "," + el.lon)
.join(";")
This will result in a new field with the value 2,3;4,5.
You can then split the new multi valued field on the semicolon ";" to obtain separated values (2,3 and 4,5).
Another approach would be to split the JSON Array elements into separate rows, avoiding the forEach and filter functions.
I can't figure out how to remove null values (and corresponding keys) from the output JSON.
Using JSON GENERATE, I am creating JSON output and in output I am getting \U0000 as null.
I want to identify and remove null values and keys from the JSON.
Cobol version - 6.1.0
I am generating a file with PIC X(5000). Tried INSPECT and other statements but no luck :(
For example :
{
"Item": "A",
"Price": 12.23,
"Qty": 123
},
{
"Item": "B",
"Price": \u000,
"Qty": 234
},
{
"Item": "C",
"Price": 23.2,
"Qty": \u0000
}
In output I want:
{
"Item": "A",
"Price": 12.23,
"Qty": 123
},
{
"Item": "B",
"Qty": 234
},
{
"Item": "C",
"Price": 23.2,
}
Approach 1:
created JSON using JSON GENERATE command and defined output file in
PIC X(50000).
after converting to UTF-8 trying to use INSPECT to find the '\U000' using hex value but there is no effect on UTF8 arguments and not able to search '\U000' values.
perform varying utf-8-pos from 1 by 1
until utf-8-pos = utf-8-end
EVAUATE TRUE
WHEN JSONOUT(1:utf-8-pos) = X'5C' *> first finding a "\" value in o/p
perform varying utf-8-pos from 1 by 1
until JSONOUT(1:utf-8-pos) = x'22' *> second finding a end position string " as X'22'
move JSONOUT(1: utf-8-end - utf-8-pos) to JSONOUT *> skip the position of null
end-perform
WHEN JSONOUT(1:utf-8-pos) NOT= X'5C'
continue
WHEN OTHER
continue
END-EVALUATE
end-perform
Approach 2:
Convert the item to UTF-16 in a national data item by using NATIONAL-OF function.
using INSPECT, EVALUATE OR PERFORM to find '\U000' using hex value N'005C'.
but not able to find the correct position of '\u000' also tried NX'005C' to find but no luck.
IDENTIFICATION DIVISION.
PROGRAM-ID. JSONTEST.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SOURCE-COMPUTER. IBM-370-158.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 root.
05 header.
10 systemId PIC X(10).
10 timestamp PIC X(30).
05 payload.
10 customerid PIC X(10).
77 Msglength PIC 9(05).
77 utf-8-pos PIC 9(05).
77 utf-8-end PIC 9(05).
01 jsonout PIC X(30000).
PROCEDURE DIVISION.
MAIN SECTION.
MOVE "2012-12-18T12:43:37.464Z" to timestamp
MOVE LOW-VALUES to customerid
JSON GENERATE jsonout
FROM root
COUNT IN Msglength
NAME OF root is OMITTED
systemId IS 'id'
ON EXCEPTION
DISPLAY 'JSON EXCEPTION'
STOP RUN
END-JSON
DISPLAY jsonout (1:Msglength)
PERFORM skipnull.
MAIN-EXIT.
EXIT.
Skipnull SECTION.
perform varying utf-8-pos from 1 by 1
until utf-8-pos = utf-8-end
EVALUATE TRUE
WHEN JSONOUT(1:utf-8-pos) = X'5C' *> first finding a "\" value in o/p
perform varying utf-8-pos from 1 by 1
until JSONOUT(1:utf-8-pos) = x'22' *> second finding a end position string " as X'22'
move JSONOUT(1: utf-8-end - utf-8-pos) to JSONOUT *> skip the position of null
end-perform
WHEN JSONOUT(1:utf-8-pos) NOT= X'5C'
continue
WHEN OTHER
continue
END-EVALUATE
end-perform.
Skipnull-exit.
EXIT.
sample output : As we don't have any value to fill for customer id so in o/p results we are getting :
{"header" : {
"timestamp" : "2012-12-18T12:43:37.464Z",
"customerid" : "\u0000\u0000\u00000" }
}
in result I want to skip customerid from the o/p. I want to skip both object:Name from the o/p file.
Since Enterprise COBOL's JSON GENERATE is an all-in-one-go command I don't think there's an easy way to do this in V6.1.
Just to give you something to look forward to: Enterprise-COBOL 6.3 offers an extended SUPPRESS-clause that does just what you need:
JSON GENERATE JSONOUT FROM MYDATA COUNT JSONLEN
SUPPRESS WHEN ZERO
ON EXCEPTION DISPLAY 'ERROR JSON-CODE: ' JSON-CODE
NOT ON EXCEPTION DISPLAY 'JSON GENERATED - LEN=' JSONLEN
You can also suppress WHEN SPACES, WHEN LOW-VALUE or WHEN HIGH-VALUE.
You can also limit suppression to certain fields:
SUPPRESS Price WHEN ZERO
Qty WHEN ZERO
Unfortunately this feature hasn't been backported to 6.1 yet (it's been added to 6.2 with the December 2020 PTF) and I don't know whether it will be...
I don't know anything about cobol but I was need the same thing in javascript, so I will share my javascript function for you. If you can translate this to cobol maybe it will help to you.
function clearMyJson(obj) {
for (var i in obj) {
if ($.isArray(obj[i]))
if (obj[i].length == 0) //remove empty arrays
delete obj[i];
else
clearMyJson(obj[i]); //calling function for clear the array
else if ($.isPlainObject(obj[i]))
if ((obj[i] == null || obj[i] == "")) // delete property if its null or empty
delete obj[i];
else
clearMyJson(obj[i]); //calling function for clear the object
}
}
All my university notes are in JSON format and when I get a set of practical questions from a pdf it is formatted like this:
1. Download and compile the code. Run the example to get an understanding of how it works. (Note that both
threads write to the standard output, and so there is some mixing up of the two conceptual streams, but this
is an interface issue, not of concern in this course.)
2. Explore the classes SumTask and StringTask as well as the abstract class Task.
3. Modify StringTask.java so that it also writes out “Executing a StringTask task” when the execute() method is
called.
4. Create a new subclass of Task called ProdTask that prints out the product of a small array of int. (You will have
to add another option in TaskGenerationThread.java to allow the user to generate a ProdTask for the queue.)
Note: you might notice strange behaviour with a naïve implementation of this and an array of int that is larger
than 7 items with numbers varying between 0 (inclusive) and 20 (exclusive); see ProdTask.java in the answer
for a discussion.
5. Play with the behaviour of the processing thread so that it polls more frequently and a larger number of times,
but “pop()”s off only the first task in the queue and executes it.
6. Remove the “taskType” member variable definition from the abstract Task class. Then add statements such as
the following to the SumTask class definition:
private static final String taskType = "SumTask";
Investigate what “static” and “final” mean.
7. More challenging: write an interface and modify the SumTask, StringTask and ProdTask classes so that they
implement this interface. Here’s an example interface:
What I would like to do is copy it into vim and execute a find and replace to convert it into this:
"1": {
"Task": "Download and compile the code. Run the example to get an understanding of how it works. (Note that both threads write to the standard output, and so there is some mixing up of the two conceptual streams, but this is an interface issue, not of concern in this course.)",
"Solution": ""
},
"2": {
"Task": "Explore the classes SumTask and StringTask as well as the abstract class Task.",
"Solution": ""
},
"3": {
"Task": "Modify StringTask.java so that it also writes out “Executing a StringTask task” when the execute() method is called.",
"Solution": ""
},
"4": {
"Task": "Create a new subclass of Task called ProdTask that prints out the product of a small array of int. (You will have to add another option in TaskGenerationThread.java to allow the user to generate a ProdTask for the queue.) Note: you might notice strange behaviour with a naïve implementation of this and an array of int that is larger than 7 items with numbers varying between 0 (inclusive) and 20 (exclusive); see ProdTask.java in the answer for a discussion.",
"Solution": ""
},
"5": {
"Task": "Play with the behaviour of the processing thread so that it polls more frequently and a larger number of times, but “pop()”s off only the first task in the queue and executes it.",
"Solution": ""
},
"6": {
"Task": "Remove the “taskType” member variable definition from the abstract Task class. Then add statements such as the following to the SumTask class definition: private static final String taskType = 'SumTask'; Investigate what “static” and “final” mean.",
"Solution": ""
},
"7": {
"Task": "More challenging: write an interface and modify the SumTask, StringTask and ProdTask classes so that they implement this interface. Here’s an example interface:",
"Solution": ""
}
After trying to figure this out during the practical (instead of actually doing the practical) this is the closest I got:
%s/\([1-9][1-9]*\)\. \(\_.\{-}\)--end--/"\1": {\r "Task": "\2",\r"Solution": "" \r},/g
The 3 problems with this are
I have to add --end-- to the end of each question. I would like it to know when the question ends by looking ahead to a line which starts with [1-9][1-9]*. unfortunately when I search for that It also replaces that part.
This keeps all the new lines within the question (which is invalid in JSON). I would like it to remove the new lines.
The last entry should not contain a "," after the input because that would also be invalid JSON (Note I don't mind this very much as it is easy to remove the last "," manually)
Please keep in mind I am very bad at regular expressions and one of the reasons I am doing this is to learn more about regex so please explain any regex you post as a solution.
In two steps:
%s/\n/\ /g
to solve problem 2, and then:
%s/\([1-9][1-9]*\)\. \(\_.\{-}\([1-9][1-9]*\. \|\%$\)\#=\)/"\1": {\r "Task": "\2",\r"Solution": "" \r},\r/g
to solve problem 1.
You can solve problem 3 with another replace round. Also, my solution inserts an unwanted extra space at the end of the task entries. Try to remove it yourself.
Short explanation of what I have added:
\|: or;
\%$: end of file;
\#=: find but don't include in match.
If each item sits in single line, I would transform the text with macro, it is shorter and more straightforward than the :s:
I"<esc>f.s": {<enter>"Task": "<esc>A"<enter>"Solution": ""<enter>},<esc>+
Record this macro in a register, like q, then you can just replay it like 100#q to do the transformation.
Note that
the result will leave a comma , and the end, just remove it.
You can also add indentations during your macro recording, then your json will be "pretty printed". Or you can make it sexy later with other tool.
You could probably do this with one large regular expression, but that quickly becomes unmaintainable. I would break the task up into 3 steps instead:
Separate each numbered step into its own paragraph .
Put each paragraph on its own line .
Generate the JSON .
Taken together:
%s/^[0-9]\+\./\r&/
%s/\(\S\)\n\(\S\)/\1 \2/
%s/^\([0-9]\+\)\. *\(.*\)/"\1": {\r "Task": "\2",\r "Solution": ""\r},/
This solution also leaves a comma after the last element. This can be removed with:
$s/,//
Explanation
%s/^[0-9]\+\./\r&/ this matches a line starting with a number followed by a dot, e.g. 1., 8., 13., 131, etc. and replaces it with a newline (\r) followed by the match (&).
%s/\(\S\)\n\(\S\)/\1 \2/ this removes any newline that is flanked by non-white-space characters (\S).
%s/^\([0-9]\+\)\. *\(.*\) ... capture the number and text in \1 and \2.
... /"\1": {\r "Task": "\2",\r "Solution": ""\r},/ format text appropriately.
Alternative way using sed, awk and jq
You can perform steps one and two from above straightforwardly with sed and awk:
sed 's/^[0-9]\+\./\n&/' infile
awk '$1=$1; { print "\n" }' RS= ORS=' '
Using jq for the third step ensures that the output is valid JSON:
jq -R 'match("([0-9]+). *(.*)") | .captures | {(.[0].string): { "Task": (.[1].string), "Solution": "" } }'
Here as one command line:
sed 's/^[0-9]\+\./\n&/' infile |
awk '$1=$1; { print "\n" }' RS= ORS=' ' |
jq -R 'match("([0-9]+). *(.*)") | .captures | {(.[0].string): { "Task": (.[1].string), "Solution": "" } }'
Problem:
I'm relatively new to programming and learning Ruby, I've worked with JSON before but have been stumped by this problem.
I'm taking a hash, running hash.to_json, and returning a json object that looks like this:
'quantity' =
{
"line_1": {
"row": "1",
"productNumber": "111",
"availableQuantity": "4"
},
"line_2": {
"row": "2",
"productNumber": "112",
"availableQuantity": "6"
},
"line_3": {
"row": "3",
"productNumber": "113",
"availableQuantity": "10"
}
I want to find the 'availableQuantity' value that's greater than 5 and return the line number.
Further, I'd like to return the line number and the product number.
What I've tried
I've been searching on using a wildcard in a JSON query to get over the "line_" value for each entry, but with no luck.
to simply identify a value for 'availableQuantity' within the JSON object greater than 5:
q = JSON.parse(quantity)
q.find {|key| key["availableQuantity"] > 5}
However this returns the error: "{TypeError}no implicit conversion of String into Integer."
I've googled this error but I can not understand what it means in the context of this problem.
or even
q.find {|key, value| value > 2}
which returns the error: "undefined method `>' for {"row"=>"1", "productNumber"=>111, "availableQuantity"=>4}:Hash"
This attempt looks so simplistic I'm ashamed, but it reveals a fundamental gap in my understanding of how to work with looping over stuff using enumerable.
Can anyone help explain a solution, and ideally what the steps in the solution mean? For example, does the solution require use of an enumerable with find? Or does Ruby handle a direct query to the json?
This would help my learning considerably.
I want to find the 'availableQuantity' value that's greater than 5 and [...] return the line number and the product number.
First problem: your value is not a number, so you can't compare it to 5. You need to_i to convert.
Second problem: getting the line number is easiest with regular expressions. /\d+/ is "any consecutive digits". Combining that...
q.select { |key, value|
value['availableQuantity'].to_i > 5
}.map { |key, value|
[key[/\d+/].to_i, value['productNumber'].to_i]
}
# => [[2, 112], [3, 113]]