Importjson to google spreadsheet, one field gives several results - json

I am trying to make a tracking system for our post # work, and so far I feel that I am 90% done, but I get one problem.
I have added this script to google spreadsheet:https://gist.github.com/chrislkeller/5719258
Now, I am trying to pull out data with this "command":
=ImportJSON("https://tracking.bring.com/api/tracking.json?q="&A4;"/consignmentSet/packageSet/eventSet/recipientSignature,/consignmentSet/packageSet/eventSet/status,/consignmentSet/senderReference,/consignmentSet/packageSet/productName";"noInherit,noHeaders")
And this works, but it gives me multiple results on status:
/consignmentSet/packageSet/eventSet/status
If you see source, it have several "STATUS" in the source https://tracking.bring.com/api/tracking.json?q=TESTPACKAGE-AT-PICKUPPOINT, and this is the reason why it get all the results. What I want, is to get the last result for status in my google spreadsheet. This to give it a more clean look, and also to have less used cells for no use. The url provided, is a test package for bring, but when we send out, it will get even more "STATUS" up to 5 different status.
TLDR: Want to get one result for status, not several as shown in--> https://tracking.bring.com/api/tracking.json?q=TESTPACKAGE-AT-PICKUPPOINT when importing data to google spreadsheet.

You need to modify the final output array. For that add/modify the following lines to the parseJSONObject_ function
var temp = data.slice(1);
var final_data = [[temp[0][0],temp[temp.length-1][1],temp[temp.length-1][2],temp[0][3]]];
return hasOption_(options, "noHeaders") ? (data.length > 1 ? final_data : new Array()) : data;
Entire Function:
/**
* Parses a JSON object and returns a two-dimensional array containing the data of that object.
*/
function parseJSONObject_(object, query, options, includeFunc, transformFunc) {
var headers = new Array();
var data = new Array();
if (query && !Array.isArray(query) && query.toString().indexOf(",") != -1) {
query = query.toString().split(",");
}
if (options) {
options = options.toString().split(",");
}
parseData_(headers, data, "", 1, object, query, options, includeFunc);
parseHeaders_(headers, data);
transformData_(data, options, transformFunc);
var temp = data.slice(1);
var final_data = [[temp[0][0],temp[0][1],temp[0][2],temp[0][3]]]; // Modified the index of the array
return hasOption_(options, "noHeaders") ? (data.length > 1 ? final_data : new Array()) : data;
}

Related

Finding ImportJSON Query Path in Google Sheets

Using Google Sheets with the custom IMPORTJSON function, I'm trying to parse the results of this URL so that it returns only the nodes with "highs".
Using the below formula, where cell A1 is the above URL, returns a #REF! error:
=ImportJSON(A1, "/data//highs", "noInherit, noTruncate")
I've also tried other variations of the /data//highs query (which would usually work with the IMPORTXML function) in the formula with the same result.
DESIRED OUTPUT
The query should result in displaying the records under "highs", i.e., both /data/nasdaq/highs and /data/nyse/highs.
The below image is the output for /data/nasdaq/highs. I'm looking to combine that query result with that for /data/nyse/highs in a single call.
Using ImportJSON:
One option could be to just separate both queries by a comma:
=ImportJSON(A1, "/data/nasdaq/highs,/data/nyse/highs", "noInherit, noTruncate")
This has several potential downsides, though: it won't automatically detect any additional ticket that has highs, but just nasdaq and nyse. If you wanted others, you'd have to edit your query.
Also, it will return nasdaq and nyse values in multiple columns, which I assume is not what you want.
Writing an alternative function:
Alternatively, since importJson cannot handle queries like //data/*/highs, I'd suggest writing a different custom function to handle this. To do this, select Extensions > Apps Script and copy the following function (see inline comments):
function altImportJson(url, query) {
const jsondata = UrlFetchApp.fetch(url); // Get data
let object = JSON.parse(jsondata.getContentText());
const levels = query.split("/").filter(String); // Get query levels
for (let i = 0; i < levels.length; i++) { // Iterate through query levels
const current = levels[i];
const last = levels[i-1];
const next = levels[i+1];
if (current != "*" && last != "*") object = object[current]; // Regular level, not "*"
else if (current == "*") { // Handle "*"
object = Object.values(object)
.filter(o => o[next])
.map(o => o[next])
.flat();
}
};
const headers = [...new Set(object.map(item => Object.keys(item)).flat())];
const data = object.map(item => { // Transform object to 2D array
let row = new Array(headers.length);
const entries = Object.entries(item);
entries.forEach(entry => {
const columnIndex = headers.indexOf(entry[0]);
if (columnIndex > -1) row[columnIndex] = entry[1];
});
return row;
});
data.unshift(headers);
return data;
}
After copying this function and saving your script, you can now call it:
=altImportJson(A1, "//data/*/highs")

Google Apps Script function called from sheet - sometimes blank results (returned empty value)

My simple (as I initially thought) Google Apps script function is not working as expected as it yields different results based on the input given = it is to return generated numbers (separated by comma) from START to END - being called in [column J] as per attached picture.
When the custom function is called with two params (start number and end number) from the sheet (cell content) it yields different results if:
Scenario #1: params are static in the formula - for instance: =FillNums(5,12)
Scenario #2: params are dynamic in the formula (for instance pointing to cell A2 and B2: =FillNums(A2,B2))
In first scenario all the calculations are OK, as expected
In the SECOND scenario however it yields sometimes BLANK result.
ONLY in SCENARO #2, depending on the input parameters (in cells), I'm getting different output:
5,21 => blank output
1,21 => 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21
2,21 => 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21
3,21 => blank output
4,19 => blank output
5,19 => blank output
6,19 => blank output
7,19 => blank output
8,19 => blank output
9,19 => blank output
5,9 => 5,6,7,8,9
10,21 => 10,11,12,13,14,15,16,17,18,19,20,21
Screenshot of the mentioned issue / Google Sheet
Debugging is not working, useless, spent hours trying to resolve it and found out that it is not possible when function being called from the sheet, the same goes for Logger = unusable.
Function:
function FillNums(start,end) {
var result = ""
var a = start
var b = end
for (var i = a; i <= b; i++) {
if (i == b) {
result = result + i
} else {
result = result + i + ","
}
}
return result
}
Any solutions are mostly welcome to resolve this issue. That function should yield ALL THE TIME row of numbers from start to end.
I do script from time to time, but I'm just a beginner here and cant understand why such simple thing is yielding empty result sometimes :(
Link to a example sheet containing the script function: Google Sheet
When I saw your script and your sample Spreadsheet, I thought that in your situation, the values of start,end of FillNums(start,end) might be the string values. I thought that this might be the reason of your issue. When this is reflected in your script, it becomes as follows.
Modified script 1:
function FillNums(start, konec) {
var result = ""
var a = Number(start); // Modified
var b = Number(konec); // Modified
var i = 0
for (i = a; i <= b; i++) {
if (i == b) {
result = result + i
} else {
result = result + i + ","
}
}
return result
}
Modified script 2:
As other sample, when an array is used, it becomes as follows.
function FillNums(start, konec) {
var a = Number(start);
var b = Number(konec);
var result = [];
for (var i = a; i <= b; i++) {
result.push(i);
}
return result.join(",");
}
Modified script 3:
As other sample, when an array is used, it becomes as follows. In this case, you can use this script like =FillNums2(A2:B10).
function FillNums2(values) {
return values.map(([start, konec]) => {
if (!start && !konec) return ["no data"];
var a = Number(start);
var b = Number(konec);
var temp = [];
for (var i = a; i <= b; i++) {
temp.push(i);
}
return [temp.join(",")];
});
}

Pulling PubMed data into Google Sheets

I'm looking for some help. I am trying to grab an author's publications from PubMed and populate the data into Google Sheets using Apps Script. I've gotten as far as the code below and am now stuck.
Basically, what I have done was first pull all the Pubmed IDs from a particular author whose name comes from the name of the sheet. Then I have tried creating a loop to go through each Pubmed ID JSON summary and pull each field I want. I have been able to pull the pub date. I had set it up with the idea that I would do a loop for each field of that PMID I want, store it in an array, and then return it to my sheet. However, I'm now stuck trying to get the second field - title - and all the subsequent fields (e.g. authors, last author, first author, etc.)
Any help would be greatly appreciated.
function IMPORTPMID(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var author = sheet.getSheetName();
var url = ("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=" + author + "[author]&retmode=json&retmax=1000");
var response = UrlFetchApp.fetch(url);
var AllAuthorPMID = JSON.parse(response.getContentText());
var xpath = "esearchresult/idlist";
var patharray = xpath.split("/");
for (var i = 0; i < patharray.length; i++) {
AllAuthorPMID = AllAuthorPMID[patharray[i]];
}
var PMID = AllAuthorPMID;
var PDparsearray = [PMID.length];
var titleparsearray = [PMID.length];
for (var x = 0; x < PMID.length; x++) {
var urlsum = ("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi?db=pubmed&retmode=json&rettype=abstract&id=" + PMID[x]);
var ressum = UrlFetchApp.fetch(urlsum);
var contentsum = ressum.getContentText();
var jsonsum = JSON.parse(contentsum);
var PDpath = "result/" + PMID[x] + "/pubdate";
var titlepath = "result/" + PMID[x] + "/title";
var PDpatharray = PDpath.split("/");
var titlepatharray = titlepath.split("/");
for (var j = 0; j < PDpatharray.length; j++) {
var jsonsum = jsonsum[PDpatharray[j]];
}
PDparsearray[x] = jsonsum;
}
var tempArr = [];
for (var obj in AllAuthorPMID) {
tempArr.push([obj, AllAuthorPMID[obj], PDparsearray[obj]]);
}
return tempArr;
}
From a PubMed JSON response for a given PubMed ID, you should be able to determine the fieldnames (and paths to them) that you want to include in your summary report. Reading them all is simpler to implement if they are all at the same level, but if some are properties of a sub-field, you can still access them if you give the right path in your setup.
Consider the "source JSON":
[
{ "pubMedId": "1234",
"name": "Jay Sahn",
"publications": [
{ "pubId": "abcd",
"issn": "A1B2C3",
"title": "Dynamic JSON Parsing: A Journey into Madness",
"authors": [
{ "pubMedId": "1234" },
{ "pubMedId": "2345" }
]
},
{ "pubId": "efgh",
...
},
...
],
...
},
...
]
The pubId and issn fields would be at the same level, while the publications and authors would not.
You can retrieve both the pubMedId and publications fields (and others you desire) in the same loop by either 1) hard-coding the field access, or 2) writing code that parses a field path and supplying field paths.
Option 1 is likely to be faster, but much less flexible if you suddenly want to get a new field, since you have to remember how to write the code to access that field, along with where to insert it, etc. God save you if the API changes.
Option 2 is harder to get right, but once right, will (should) work for any field you (properly) specify. Getting a new field is as easy as writing the path to it in the relevant config variable. There are possibly libraries that will do this for you.
To convert the above into spreadsheet rows (one per pubMedId in the outer array, e.g. the IDs you queried their API for), consider this example code:
function foo() {
const sheet = /* get a sheet reference somehow */;
const resp = UrlFetchApp.fetch(...).getContentText();
const data = JSON.parse(resp);
// paths relative to the outermost field, which for the imaginary source is an array of "author" objects
const fields = ['pubMedId', 'name', 'publications/pubId', 'publications/title', 'publications/authors/pubMedId'];
const output = data.map(function (author) {
var row = fields.map(function (f) {
var desiredField = f.split('/').reduce(delve_, author);
return JSON.stringify(desiredField);
});
return row;
});
sheet.getRange(1, 1, output.length, output[0].length).setValues(output);
}
function delve_(parentObj, property, i, fullPath) {
// Dive into the given object to get the path. If the parent is an array, access its elements.
if (parentObj === undefined)
return;
// Simple case: parentObj is an Object, and property exists.
const child = parentObj[property];
if (child)
return child;
// Not a direct property / index, so perhaps a property on an object in an Array.
if (parentObj.constructor === Array)
return collate_(parentObj, fullPath.splice(i));
console.warn({message: "Unhandled case / missing property",
args: {parent: parentObj, prop: property, index: i, pathArray: fullPath}});
return; // property didn't exist, user error.
}
function collate_(arr, fields) {
// Obtain the given property from all elements of the array.
const results = arr.map(function (element) {
return fields.slice().reduce(delve_, element);
});
return results;
}
Executing this yields the following output in Stackdriver:
Obviously you probably want some different (aka real) fields, and probably have other ideas for how to report them, so I leave that portion up to the reader.
Anyone with improvements to the above is welcome to submit a PR.
Recommended Reading:
Array#reduce
Array#map
Array#splice
Array#slice
Internet references on parsing nested JSON. There are a lot.

Fill multiple rows in google spreadsheet via google script

I am currently working on a semester project for my university in which we want to log data from an Arduino to a Google Sheet.
I was following the numerous tutorials and examples that I could find on Google and it worked so far really, really well. My Arduino is able to upload data to said spreadsheet.
Unfortunately all those examples always only deal with one row to be filled. For our project we would like to fill 2 or 3 lines simultaneously.
I will shortly show what I have done so far and maybe you can help me solve my (probably easy) problem.
I created a google spreadsheet in which I want to log my data
I used the script from a tutorial that should fill one row.
By typing the following line in my browserhttps://script.google.com/macros/s/<gscript id>/exec?tempData=datahereI am now able to fill row one with my data in enter in the end of the url.
But how do I progress now, when I want to fill two or three rows of the table? I say that the author of the code already implemented an option to fill the third row, yet I can't find out what to input in my url then to fill it with data.
All my attempts to write something like
https://script.google.com/macros/s/<gscript id>/exec?tempData=datahere&tempData1=value2
just ended in writing
datahere&tempData1=value2
in my first row, not filling datahere into the first and value2 in to the second row.
How can I provide and write multiple rows of data?
The code in this script is:
/*
GET request query:
https://script.google.com/macros/s/<gscript id>/exec?tempData=data_here
*/
/* Using spreadsheet API */
function doGet(e) {
Logger.log( JSON.stringify(e) ); // view parameters
var result = 'Ok'; // assume success
if (e.parameter == undefined) {
result = 'No Parameters';
}
else {
var id = '<ssheet id>'; // Spreadsheet ID
var sheet = SpreadsheetApp.openById(id).getActiveSheet();
var newRow = sheet.getLastRow() + 1;
var rowData = [];
//var waktu = new Date();
rowData[0] = new Date(); // Timestamp in column A
for (var param in e.parameter) {
Logger.log('In for loop, param='+param);
var value = stripQuotes(e.parameter[param]);
//Logger.log(param + ':' + e.parameter[param]);
switch (param) {
case 'tempData': //Parameter
rowData[1] = value; //Value in column B
break;
case 'tempData1':
rowData[2] = value; //Value in column C
break;
default:
result = "unsupported parameter";
}
}
Logger.log(JSON.stringify(rowData));
// Write new row below
var newRange = sheet.getRange(newRow, 1, 1, rowData.length);
newRange.setValues([rowData]);
}
// Return result of operation
return ContentService.createTextOutput(result);
}
/**
* Remove leading and trailing single or double quotes
*/
function stripQuotes( value ) {
return value.replace(/^["']|['"]$/g, "");
}
I would suggest the following:
Create a 2d array of your data you wish to write to the spreadsheet. If your client on Arduino were using JavaScript this might look like :
var data = [
["row1value1", "row1value2"],
["row2value1", "row2value2"]
];
Convert this to JSON, again in JavaScript this might look like:
var json = JSON.stringify(data);
This gives you a string representation of your array.
Now make your request using this data. I would suggest you should look at using doPost instead of doGet, as you are sending data to the spreadsheet that updates state. However, for the purposes of getting something working, your URL would look like:
https://script.google.com/<.....>/exec?myarray=<stringified JSON>
In Apps Script, in your doGet (again, consider using doPost instead), you could then use:
// Get the JSON representation of the array:
var json = e.parameter.myarray;
// Convert back to 2d array
var data = JSON.parse(json);
Now you can write this to a Range in Sheets using setValues, e.g. assuming a rectangular 2d array:
sheet.getRange(1, 1, data.length, data[0].length).setValues(data);
Hope this helps

Error when trying to store an array in ScriptDb

I have an array of objects that is created by my script and I am trying to copy that array into a new array and then store it in scriptDb using the following function:
function copyAndStore (currentArray) {
var db = ScriptDb.getMyDb();
var copyArray = [];
for (var i in currentArray) {
copyArray.push(currentArray[i]);
}
var id = db.save(copyArray);
return id;
}
It copies everything properly but when it gets to var id = db.save(copyArray); I get the error: Invalid argument. Expected a javascript map object.
Does ScriptDb have issues with storing arrays? Thanks in advance for the help.
As #Thomas said, you can save an array in a map object.
You don't need to perform a copy operation before putting an object into the ScriptDB, either. You could save your array by simply db.save({myArray}), and remember the ID.
Here's some minimalist code to demonstrate. I'm showing two ways to retrieve your saved array - one by ID, which seems to be the way you were planning to, but also a second way using a "key" value for a query. If you expect to retrieve the contents of ScriptDB in a later run of your code, this approach eliminates the need to somehow remember the ID of the stored array.
function saveArray (currentArray) {
var db = ScriptDb.getMyDb();
return db.save({type: "savedArray", data:currentArray}).getId();
}
function loadArrayById (id) {
var db = ScriptDb.getMyDb();
return db.load(id).data;
}
function loadArrayByType () {
var db = ScriptDb.getMyDb();
var result = db.query({type: "savedArray"});
if (result.hasNext()) {
return result.next().data;
}
else {
return [];
}
}
function test() {
var arr = ['this','is','a','test'];
var savedId = saveArray( arr );
var loaded1 = loadArrayById( savedId );
var loaded2 = loadArrayByType();
debugger; // pause if running debugger
}
Here's what you'll see at the debugger pause:
Note that by using the map tag data to pull the array from the saved object, both loaded1 and loaded2 are identical to the source array arr.
ScriptDb only stores map objects. You could however store a map that contains an array!
You can use arrays to save several objects in a single call using db.saveBatch.