How do I force jsonStringify to output an array without quotes? - javascript

I am using a Google Apps script to format data from a Google spreadsheet into geoJSON by calling jsonStringify. My code is modified from this original example. Each row in the spreadsheet represents a country, with a single cell containing the relevant geoJSON polygon data:
[[[74.92,37.24],[74.57,37.03],...[73.31,37.46],[74.92,37.24]]]
But since that cell doesn't contain a number, jsonStringify is parsing it as a string, returning this:
"coordinates":["[[[74.92,37.24],[74.57,37.03],...[73.31,37.46],[74.92,37.24]]]"]
...which I can't use, since the quotes make it non-valid geoJSON. When I enter a single number, jsonStringify returns it without the quotes, I assume because it recognizes that it's a number. Is there some way I can force it to interpret the polygon data as a number or otherwise escape the quotes? Here is the relevant section of my code:
function getObjects(data, keys) {
var objects = [];
var headers = getHeaders(sheet, activeRange, 1);
var zip = function(keys, data) {
var obj = {};
for (var i = 0; i < keys.length; i++) {
obj[keys[i]] = data[i];
}
return obj;
};
for (var i = 0; i < data.length; i++) {
var obj = zip(headers, data[i]);
//this is the polygon data
var poly = obj[settings.poly];
var coordinates = [poly];
// If we have an id and polygon data
if (obj[settings.id] && poly) {
// Define a new GeoJSON feature object
var feature = {
type: 'Feature',
// Get ID from UI
id: obj[settings.id],
geometry: {
type: 'MultiPolygon',
// Get coordinates from UIr
coordinates: coordinates
},
// Place holder for properties object
properties: obj
};
objects.push(feature);
}
}
return objects;
}

Instead of passing a string to jsonStringify as the "coordinates", pass an actual array. That is, use jsonParse first on the original text (as required) to obtain the relevant object.
An example conversion (with guard that may or may not be appropriate) might look like:
function restoreCoordinatesFromText(source) {
var geo = Utilities.jsonParse(source);
return (geo && (geo instanceof Array)
? geo // looks like we got our [array of stuff]
: theSourceData); // dunno, let's act like we never saw it
}
Then later on the actual coordinate array can be encoded correctly in JSON while the string exhibits the initial undesired behavior.
var coords = "[1,2,3,4]"; /* just a string */
var json = Utilities.jsonStringify({
/* array, good! */
goodCoordinates: restoreCoordinatesFromText(coords),
/* string, bad! */
badCoordinates: coords
});
Here is the corresponding fiddle (it utilizes ES5 JSON support).

Related

How can I get a string literal (character array?) from a value in javascript?

I am using Google App Script to grab text from a Google Sheet, then upload it to Firebase. JSON encoding of any data from the sheet is adding extra escape characters that I don't need. I am trying to figure out a way to prevent this because it does not add unnecessary escape characters if I use a string literal.
This function below is what I call to sync data from the sheet to firebase. I initialize an object from the gathered data, and then I send it off to the Firebase database for updating.
If I keep it as it is, the string at data[i][1] eventually has escape characters automatically added in. If I replace data[i][1] with any literal string (i.e. "Testing\n\n-Caleb") the value of that key keeps its entire string un-modified.
// Gets the data from the sheet and puts it in an object.
// It is called by running the sync button in the sheet.
function sync() {
// get the spreadsheet data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("main");
var [rows, columns] = [sheet.getLastRow(), sheet.getLastColumn()];
var data = sheet.getSheetValues(1, 1, rows, columns);
// put the data in an object
testAlert(data[1][1]); // prints "Testing
//
// -Caleb" (OK)
var obj = {}
for (var i = 1; i < data.length; i++) {
obj[i - 1] = {
Happy: data[i][0],
Meh: data[i][1], // THE VALUE OF TOPIC
//Meh: "Testing\n\n-Caleb" // THE LITERAL STRING OPTION
Down: data[i][2],
Sad: data[i][3],
Angry: data[i][4]
}
}
testAlert(obj[0].Meh). // prints "Testing
//
// -Caleb" (OK)
// convert to unused JSON object to print to the window what is happening
var jsonOBJ = JSON.stringify(obj[0].Meh)
testAlert(jsonOBJ). // prints "\"Testing\\n\\n-Caleb\"" (ERROR)
updateFirebaseData(obj, "emotions");
}
This is the function called at the end:
// updates the firebase database with specified data and location
function updateFirebaseData(obj, loc) {
var firebaseUrl = "https://gem-0-2.firebaseio.com/";
var secret = "FirebaseSecret";
var base = FirebaseApp.getDatabaseByUrl(firebaseUrl, secret);
var result = base.updateData(loc, obj);
}
The data at data[i][1] is "Testing\n\n-Caleb" and by the time it reaches the database, it is "\"Testing\n\n-Caleb\"", and so escaped characters that I intentionally place, like end-lines, aren't translated properly. However, if I put "Testing\n\n-Caleb" as the value for "Meh", then it stays as-is.
sheet.getSheetValues() is returning a string that's already encoded as JSON. If you want to store the actual string, you need to decode it with JSON.parse().
for (var i = 1; i < data.length; i++) {
obj[i - 1] = {
Happy: data[i][0],
Meh: JSON.parse(data[i][1]), // THE VALUE OF TOPIC
Down: data[i][2],
Sad: data[i][3],
Angry: data[i][4]
}

2D Array Not Translating to D3 Map Coordinates

The 2D array I created is not accepted by D3.js as coordinates (The map example I'm using is at http://bl.ocks.org/phil-pedruco/6522279.)
Here are the facts:
1.) The JSON input I am supplied with is a string of latitude and longitude (e.g. "37.3879 -134.96376").
2.) The output required is a 2D array that must be in this form [[lat,long],[lat,long],...].
3.) console.log(typeof(lat)); returns that the lat and long values are indeed numbers (not strings)
4.) console.log("Coordinates: " + JSON.stringify(points)); returns [[37.3879,-134.96376],[13.5,-45],...] - which appears to be in the correct form, yet no points are displayed on the map
5.) If I hardcode the values in the array the points are displayed.
var points = [];
//var dotSpot = [[1.4440, 32.442], [12.222, 4.893]];
window.onload = function () {
getJSON();
}
function getJSON(supplierID) {
//retrieves JSON perfectly
//calls make() with Supplier data
}
function make(jdata) {
for (var i = 0; i < jdata.length; i++) {
//Splits string by space
var temp = jdata[i].latLong.split(/[ ,]+/)
//Converts lat and long strings to floats
var lat = parseFloat(temp[0]);
var long = parseFloat(temp[1]);
console.log(typeof(lat));
//Add "curr" coordinates array to "points" array
var curr = new Array(lat, long);
points.push(curr);
}
console.log("Coordinates: " + JSON.stringify(points));
}
You have specified your coordinates to be [latitude, longitude]. However, unlike many other applications D3 uses coordinates with longitude as the first value followed by latitude. From the docs:
The point must be specified as a two-element array [longitude, latitude] in degrees.
Changing you data accordingly should yield the desired rendering:
var curr = new Array(long, lat); // instead of new Array(lat, long)
points.push(curr);

Add values from one array to object with specified key & index

Im using the following code,
jQuery.each(aDataSel, function(index, oData) {
oPushedObject = {};
aSelectedDataSet.push(fnCreateEnt(aProp, oData, oPushedObject));
});
This is aSelectedDataSet values
and this is the values of OData
What I need is that before I do the push is to fill the listTypeGroup & listTypeGroupDescription (with the red arrow ) with values that Are inside the oData -> ListTypeGroupAssigment -> result (listTypeGroup & listTypeGroupDescription) , The index is relevant since I want to add just the value of the index in each iteration (since this code is called inside outer loop and the index determine the current step of the loop) ,How it can be done nicely?
The result contain 100 entries (always) and the a selected data will have 100 entries at the end...
Update :)
Just to be clear In the pic I show the values which is hardcoded for this run but the values can be any values, we just need to find the match between the both objects values...
I mean to find a match between to_ListTypeGroupAssigment in both object (which in this case exist ) and if in oData there is result bigger then one entry start with the matching ...
UPDATE2 - when I try Dave code the following happen for each entry,
This happen in the Jquery.extend line...any idea how to overcome this?
The following hard-coded of Dave:-) work perfect but I need generic code which doesnt refer to specific field name
jQuery.each(aDataSet, function(index, oData) {
oPushedObject = {};
fnCreatePushedEntry(aProperties, oData, oPushedObject);
var result = oData.to_ListTypeGroupAssignment.results[index];
oPushedObject.to_ListTypeGroupAssignment = {
ListTypeGroup: result.ListTypeGroup,
ListTypeGroupDescription: result.ListTypeGroupDescription
};
aSelectedDataSet.push(oPushedObject);
});
Im stuck :(any idea how to proceed here ?what can be wrong with the extend ?
should I use something else ? Im new to jQuery...:)
I think that this happen(in Dave answer) because the oData[key] is contain the results and not the specified key (the keyValue = to_ListTypeGroupAssignment ) which is correct but we need the value inside the object result per index...
var needValuesForMatch = {
ListTypeGroup: 'undefined',
ListTypeGroupDescription: 'undefined',
}
//Just to show that oPushedObject can contain additional values just for simulation
var temp = {
test: 1
};
//------------------This object to_ListTypeGroupAssigment should be filled (in generic way :) ------
var oPushedObject = {
temp: temp,
to_ListTypeGroupAssignment: needValuesForMatch
};
oPushedObject is one instance in aSelectedDataSet
and after the matching I need to do the follwing:
aSelectedDataSet.push(oPushedObject);
Is this what you're after:
OPTION ONE - DEEP CLONE FROM oData TO aSelectedDataSet
aSelectedDataSet.forEach(function(currentObject,index){
for (var childObject in currentObject) {
if (! currentObject.hasOwnProperty(childObject))
continue;
var objectToClone = oData[childObject]['results'][index];
if(objectToClone)
$.extend(true,currentObject[childObject],objectToClone);
}
});
Here is your data in a fiddle with the function applied: https://jsfiddle.net/hyz0s5fe/
OPTION TWO - DEEP CLONE FROM oData ONLY WHERE PROPERTY EXISTS IN aSelectedDataSet
aSelectedDataSet.forEach(function(currentObject,index){
for (var childObject in currentObject) {
if (! currentObject.hasOwnProperty(childObject))
continue;
if(typeof currentObject[childObject] !== 'object')
continue;
for(var grandChildObject in currentObject[childObject]) {
var objectToClone = oData[childObject]['results'][index][grandChildObject];
if(typeof objectToClone === 'object') {
$.extend(true,currentObject[childObject][grandChildObject],objectToClone);
} else {
currentObject[childObject][grandChildObject] = objectToClone;
}
}
}
Fiddle for option 2: https://jsfiddle.net/4rh6tt25/
If I am understanding you correctly this should just be a small change:
jQuery.each(aDataSel, function(index, oData) {
oPushedObject = {};
fnCreateEnt(aProp, oData, oPushObj);
//get all the properties of oData and clone into matching properties of oPushObj
Object.getOwnPropertyNames(oData).forEach(function(key) {
if (oPushObj.hasOwnProperty(key)) {
//oPushObj has a matching property, start creating destination object
oPushObj[key] = {};
var source = oData[key];
var destination = oPushObj[key];
//can safely assume we are copying an object. iterate through source properties
Object.getOwnPropertyNames(source).forEach(function(sourceKey) {
var sourceItem = source[sourceKey];
//handle property differently for arrays
if (Array.isArray(sourceItem)) {
//just copy the array item from the appropriate index
destination[sourceKey] = sourceItem.slice(index, index + 1);
} else {
//use jQuery to make a full clone of sourceItem
destination[sourceKey] = $.extend(true, {}, sourceItem);
}
});
}
});
aSelectedDataSet.push(oPushedObject);
});
It is unclear what exactly your fnCreateEnt() function returns though. I am assuming it is the populated oPushObj but it's not entirely clear from your question.

JavaScript map containing arrays turns into map containing maps

I am creating a map containing arrays.
I'm sending this map as parameter to a method in a popup window.
This map of arrays is turning into a map of maps in the popup window!!
Im using IE8.
I'm creating a map like this:
var mapDetails = new Object();
mapDetails.fields = ['A','B'];
mapDetails.optSampleData = ['X','Y'];
JSON of map Im sending:
{'fields':['A','B'],'optSampleData':['X','Y']}
JSON of map Im receiving:
{'fields':{'0':'A','1':'B'},'optSampleData':{'0':'X','1':'Y'}}
This wouldnt be a problem in JS as the data can still be accessed as fields[0]. But, I'm sending it to server-side where I'm using GSON to parse the JSON. GSON turns it into a map.
Try this function
function myParser(str) {
str = str.replace(/'/g, '"');
var obj = JSON.parse(str);
for(var item in obj) {
if(obj.hasOwnProperty(item)) {
var array = [];
for(var arrItem in obj[item]) {
if(obj[item].hasOwnProperty(arrItem)) {
array.push(obj[item][arrItem]);
}
}
obj[item] = array;
}
}
return obj;
}
You can pass the received string into this function and receive your desired map containing arrays
Also you can reform your received string into your desired string format by this function:
var str = "{'fields':{'0':'A','1':'B'},'optSampleData':{'0':'X','1':'Y'}}";
var obj = myParser(str);
var desiredFormat = JSON.stringify(obj);
/* The result would be: */
/* "{"fields":["A","B"],"optSampleData":["X","Y"]}" */
Hope it helps you :)

Creating hash array in Google Apps Script

I've been trying to work with Trello and the Google Apps Script this week. I am trying to create an array of hashes that I can then use to load the spreadsheet. Google apps script doesn't like the typical javascript code of creating hashes. I've looked up the docs but they don't have anything like hashes...they say to:
var object = [];
var object1 = {};
object.push(object1);
This wont work because I'm essentially trying to do something like:
var hash={name: , label: };
var n= someNumber;
var l= someLabel
var hash.push(name: n, label: l);
Essentially that is the code I have right now. But here is my entire function:
function getData(){
var list={};
//get the list of delivered cards from Trello
var listRequest = authorizeToTrello(); // get authorization
var result = UrlFetchApp.fetch("https://trello.com/1/lists/4fea3a2c3a7038911ebff2d8/cards",
listRequest);//fetch list
var listOfCards = Utilities.jsonParse(result.getContentText());//Google app utility format json
//outer loop to iterate through list of Cards
for(var i=0; i < listOfCards.length; i++){
var cardId = listOfCards[i].id; //get the id of a single card
var l = listOfCards[i]["label"]; //get the label for the our structure
//get a json object for a single card within the list of cards iteration
var cardRequest = authorizeToTrello();
var getCard = UrlFetchApp.fetch("https://trello.com/1/cards/" + cardId + "/actions", cardRequest);
var singleCard = Utilities.jsonParse(getCard.getContentText());
//inner loop to iterate the single cards JSON objects
for(var j=0; j < singleCard.length; j++) {
if(singleCard[j].data != undefined && singleCard[j].data.listAfter != undefined)
{
var str = singleCard[j]["data"]["listAfter"]['name'];
if(str === "Delivered Q3 2012"){
var n = singleCard[j]['memberCreator']['fullName'];
}
}
}
//push the data to list
list.push(n,l);
}
return name, label; //return list for output
}
Reading the question, I understood that the author needs to know how to create an associative array in a GAS. If it is correct then here is a couple of links (here and here) and a sample code is bellow.
function testMap() {
var map = {};
map["name1"] = "value1";
map["name2"] = "value2";
return map;
}
If the author needs really
an array of hashes
then there are a couple of ways depending on which hash algorithm is required.
to use the Utilities.computeDigest method to calculate a hash of a string using one of available algorithms.
if the required hash calculation algorithm is not supported by the Utilities.computeDigest, then is possible to write own implementation as it is done for the BLAKE function.
Here is a sample of how to create an array of hashes using the MD5 hash.
function testHash() {
var array = [];
array.push(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, "value1"));
array.push(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, "value2"));
return array;
}
P.S. The return line of the author code return name, label; //return list for output
is not correct - only the label variable value is returned. To return a couple of variables as an array is necessary to write return [name, label];. Or may be the author needs to return the list variable and not name and label.
I know this is an old post / question, but i would like to update my answer since the original anwer (1st answer) is misleading. I was myself looking for how to return associative arrays back to a cell in the spreadsheet, but alas.. "YOU CANNOT". Google spreadsheet MUST want an numerically indexed array or an object. Otherwise it returns "#ERROR".
Here are the steps to replicate the issue.
function testMap() {
var map = {};
map["name1"] = "value1";
map["name2"] = "value2";
return map
Formula in your cell: =testMap()
Value in your cell: Thinking... #ERROR
Solution (rather a workaround)
1: Transfer your objects from your associative array into a numerically indexed array using for-each type loop.
var temp = new Array();
for (var i in map) {
temp.push([i,map[i]])
// optionally use activeSheet.getRange(X:X).setValue([i,map[i]])) function here.
// set values will not work in cell functions. To use it via cell functions, rerun / trigger the functions using an on_edit event.
}
If you used a temp like numerically indexed array, you can return "temp" back to the calling cell.
Summary: For onEdit() purposes, use Cache Service to define associative array data.
Here's a shared Gsheet demonstrating this curious behavior. I tried the following solution in programmatically defining an associative array based on data in a Google sheet.
var assocArr = {
labels: {},
init: function () {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('sheetName');
var values = sheet.getDataRange().getValues();
for(var row in values) {
assocArr.labels[values[row][0]] = values[row][1];
};
for(var key in assocArr.labels) {
Logger.log("key: %s, value: %s",key, assocArr.labels[key]);
};
return(void(0));
},
};
To execute this, you run the init() method in the onOpen() event handler.
function onOpen() {
assocArr.init();
var key = 'test';
SpreadsheetApp.getUi().alert( assocArr.labels[key] );
Logger.log("onOpen: key: %s, value: %s",key, assocArr.labels[key]);
};
The logger message confirms that init() loads the data from the worksheet.
Now if I try to reference this assocArr object in onEdit() it returns undefined for all key values.
function onEdit(event) {
var key = 'test';
SpreadsheetApp.getUi().alert( assocArr.labels[key] );
Logger.log("onEdit: key: %s, value: %s",key, assocArr.labels[key]);
};
I infer that for security reasons, Google limited the simple-trigger onEdit() to not have global variable scope, same as they voided the utility of the event.user property.
Now instead if I simply put the key-value pair in the cache, it works! Here is the complete code that works using the Cache Service.
var cache = CacheService.getPrivateCache();
var assocArr = {
init: function () {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Account Labels');
var values = sheet.getDataRange().getValues();
for(var row in values) {
cache.put(values[row][0], values[row][1], 3600);
};
return(void(0));
},
};
function onOpen() {
assocArr.init();
var key = 'test';
SpreadsheetApp.getUi().alert( cache.get(key) );
Logger.log("onOpen: key: %s, value: %s",key, cache.get(key));
};
function onEdit(event) {
var key = 'test';
SpreadsheetApp.getUi().alert( cache.get(key) );
Logger.log("onEdit: key: %s, value: %s",key, cache.get(key));
};
Curiously, the onEdit() has the cache variable in its scope.
Here again is the shared Gsheet demonstrating this curious behavior.
I found this really quick way that is not listed
Create a json object (array style)
var myArray = {
1:{"id": "inprogress","title" : "in Progress"},
2:{"id": "notstarted","title" : "Not Started"},
3:{"id": "completed" ,"title" : "Completed"}
};
read the json
// get the lenght of the json object
var jsonSize = Object.keys(myArray).length;
// use this in a loop
for (var i = 1; i < Object.keys(jsonSize).length; i++) {
var title = myArray[i].title;
}
Works like a charm for me

Categories