Convert Query String to Request body response error - google-apps-script

I'm trying to convert a working query string to a request body and I am receiving an error that I am missing an attribute. I received help from #Tanaike from another post that provided the encode URI function that breaks down the params into an encoded format. However, when I have 30-50 pairs in my "pairs" param, URLFetch has an error with the query string length limit. So I am trying to send it as a request body instead.
var botParams = {
"name": "TestBot",
"base_order_volume": 0.001,
"take_profit": 1.5,
"safety_order_volume": 0.001,
"martingale_volume_coefficient": 2,
"martingale_step_coefficient": 1,
"max_safety_orders": 1,
"active_safety_orders_count": 1,
"safety_order_step_percentage": 2.5,
"take_profit_type": "total",
"stop_loss_percentage": 0,
"cooldown": 0,
"pairs": [
"BTC_ADA",
"BTC_TRX"
],
"trailing_enabled":"true",
"trailing_deviation":0.5,
"strategy_list": [
{
"strategy":"cqs_telegram"
}
]
};
try {
// var totalParams = keys.reduce(function(q, e, i) {
// q += (e == "pairs" ? botParams[e].reduce(function(s, f, j) {
// s += e + "[]=" + f + (j != botParams[e].length - 1 ? "&" : "");
// return s;
// },"") : e + "=" + (typeof botParams[e] == "object" ?
//encodeURIComponent(JSON.stringify(botParams[e])) :
//encodeURIComponent(botParams[e]))) + (i != keys.length - 1 ? "&" : "");
// return q;
// }, endPoint); //Thanks to Tanaike
//Call
//Base
var baseUrl = "https://3commas.io";
//Total Endpoint
var endPoint = "/public/api/ver1/bots/274339/update?";
//Convert Bot Params + endPoint to Encoded URI
var keys = Object.keys(botParams);
var totalParams = keys.reduce(function(q, e, i) {
q += e + "=" + (typeof botParams[e] == "object" ? encodeURIComponent(JSON.stringify(botParams[e])) : encodeURIComponent(botParams[e])) + (i != keys.length - 1 ? "&" : "");
Logger.log(encodeURIComponent(botParams[e]))
return q;
},endPoint);
Logger.log(totalParams)
//Create Signature
var signature = Utilities.computeHmacSha256Signature(totalParams, secret);
//Convert from byte
signature = signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
var headers = {
"APIKEY": key,
"Signature": signature,
};
var params = {
"method": "PATCH",
"headers": headers,
"payload" : totalParams,
//Show full exceptions
muteHttpExceptions: true
};
var finalURL = baseUrl + "/public/api/ver1/bots/274339/update";
Logger.log(finalURL)
//https://3commas.io/public/api/ver1/bots/274339/update
// Call final URL with params
var data = UrlFetchApp.fetch(finalURL, params).getContentText();
var json = JSON.parse(data);
Logger.log(json)
} catch (err) {Logger.log(err)}
}
The current error I am receiving is {error_attributes={name=[is missing]}, error_description=Invalid parameters, error=record_invalid} So for some reason it's not recognizing my name attribute. When I logged the results I see the name is passed as a string.
Logged results: /public/api/ver1/bots/274339/update?name=TestBot&base_order_volume=0.001&take_profit=1.5&safety_order_volume=0.001&martingale_volume_coefficient=2&martingale_step_coefficient=1&max_safety_orders=1&active_safety_orders_count=1&safety_order_step_percentage=2.5&take_profit_type=total&stop_loss_percentage=0&cooldown=0&pairs=%5B%22BTC_ADA%22%2C%22BTC_TRX%22%5D&trailing_enabled=true&trailing_deviation=0.5&strategy_list=%5B%7B%22strategy%22%3A%22cqs_telegram%22%7D%5D
3commas docs: https://github.com/3commas-io/3commas-official-api-docs/blob/master/bots_api.md#edit-bot-permission-bots_write-security-signed
Just looking for suggestions as to what it could be. Thanks.

I think that in your case, botParams might be required to send as form, while the query parameters are required to be created from botParams for creating the signature. So the modified script is as follows.
Modified script:
var key = 'apikey';
var secret = 'apisecret';
var editBots = "/ver1/bots/274339/update";
var baseUrl = "https://3commas.io";
var endPoint = "/public/api"+editBots;
var botParams = {
"name": "TestBot",
"base_order_volume": 0.001,
"take_profit": 1.5,
"safety_order_volume": 0.001,
"martingale_volume_coefficient": 2,
"martingale_step_coefficient": 1,
"max_safety_orders": 1,
"active_safety_orders_count": 1,
"safety_order_step_percentage": 2.5,
"take_profit_type": "total",
"stop_loss_percentage": 0,
"cooldown": 0,
"pairs": ["BTC_ADA", "BTC_TRX"],
"trailing_enabled": "true",
"trailing_deviation": 0.5,
"strategy_list": [{"strategy": "cqs_telegram"}]
};
var keys = Object.keys(botParams);
var totalParams = keys.reduce(function(q, e, i) {return q += (e == "pairs" ? botParams[e].reduce(function(s, f, j) {return s += e + "=" + f + (j != botParams[e].length - 1 ? "&" : "")},"") : e + "=" + (typeof botParams[e] == "object" ? encodeURIComponent(JSON.stringify(botParams[e])) : encodeURIComponent(botParams[e]))) + (i != keys.length - 1 ? "&" : "")}, endPoint + "?");
var signature = Utilities.computeHmacSha256Signature(totalParams, secret);
signature = signature.map(function(e) {return ("0" + (e < 0 ? e + 256 : e).toString(16)).slice(-2)}).join("");
var headers = {
'APIKEY': key,
'Signature': signature,
};
var params = {
method: 'PATCH',
headers: headers,
payload: Object.keys(botParams).reduce(function(o, e) {
o[e] = typeof botParams[e] == "object" ? JSON.stringify(botParams[e]) : e;
return o;
}, {}),
muteHttpExceptions: true
};
var data = UrlFetchApp.fetch(baseUrl + endPoint, params).getContentText();
Logger.log(data)
Note:
I'm not sure about the specification of the API. So if above script didn't work, can you ask the following points to the manager of API?
About "pairs": ["BTC_ADA", "BTC_TRX"], of botParams, in this script, the same process with creating the query parameter is used like pairs=BTC_ADA&pairs=BTC_TRX. Is this correct?
Are key and secret required to be included in botParams for creating signature?
Are key and secret required to be included in the payload?
Are above both 2 and 3 required?
Edit:
Modification point:
From:
payload: Object.keys(botParams).reduce(function(o, e) {
o[e] = typeof botParams[e] == "object" ? JSON.stringify(botParams[e]) : e;
return o;
}, {}),
To:
payload: Object.keys(botParams).reduce(function(o, e) {
o[e] = typeof botParams[e] == "object" ? JSON.stringify(botParams[e]) : botParams[e];
return o;
}, {}),

I figured out the issue. It was an encoding problem as suspected. Here's what ended up working.
var totalParams2 = keys.reduce(function(q, e, i) {
q += (e == "pairs" ? botParams[e].reduce(function(s, f, j) {
s += e + encodeURIComponent("[]")+"=" + f + (j != botParams[e].length - 1 ? "&" : "");
return s;
},"") : e + "=" + (typeof botParams[e] == "object" ? encodeURIComponent(JSON.stringify(botParams[e])) : encodeURIComponent(botParams[e]))) + (i != keys.length - 1 ? "&" : "");
return q;
}, "");
The addition of e + encodeURIComponent("[]")+ specifically because of the way the API is setup. I had to encode my pairs array.

Related

Twitter Media Upload OAuth1.0a auth error

I am trying to implement twitter media upload on google apps script via OAuth1.0a... as there has been no oauth2 for media uploads since 2 years. Following is the code. Still facing 401, 402, 403 , 400... all such return codes since last one week. Is this end point not working? anyone has any info? any ideas why its failing again and again.
using OAuth1 (https://github.com/googleworkspace/apps-script-oauth1/tree/3f3a6697d95a3ed9a91d09c65ffc34941136f587)
var url = 'https://upload.twitter.com/1.1/media/upload.json?media_category=tweet_image';
var baseUrl = 'https://upload.twitter.com/1.1/media/upload.json';
var params = {
'payload': {'media': imageBlob},
'method': 'POST',
'muteHttpExceptions' : true
};
var token = JSON.parse(PropertiesService.getUserProperties().getProperty("oauth1."+ account));
var oauth_token = token.oauth_token
var oauth_token_secret = token.oauth_token_secret
var oauth_consumer_key = PropertiesService.getUserProperties().getProperty("TWITTER_CONSUMER_KEY");
var oauth_consumer_secret = PropertiesService.getUserProperties().getProperty("TWITTER_CONSUMER_SECRET");
const method = params['method'] || 'post';
params['method'] = method;
const oauthParameters = {
oauth_version: "1.0",
oauth_token: oauth_token,
oauth_consumer_key: oauth_consumer_key,
oauth_signature_method: "HMAC-SHA1",
oauth_timestamp: (Math.floor((new Date()).getTime() / 1000)).toString(),
};
oauthParameters.oauth_nonce = oauthParameters.oauth_timestamp + Math.floor(Math.random() * 100000000);
const payload = params['payload'] || {};
const q = {"media_category": "tweet_image"} //parms from url
const queryKeys = Object.keys(oauthParameters).concat(Object.keys(payload)).concat(Object.keys(q)).sort();
const baseString = queryKeys.reduce(function(acc, key, idx) {
if (idx) acc += encodeURIComponent("&");
if (oauthParameters.hasOwnProperty(key))
acc += _encode(key + "=" + oauthParameters[key]);
else if (payload.hasOwnProperty(key))
acc += _encode(key + "=" + _encode(payload[key]));
return acc;
}, method.toUpperCase() + '&' + _encode(baseUrl) + '&');
oauthParameters.oauth_signature = Utilities.base64Encode(
Utilities.computeHmacSignature(
Utilities.MacAlgorithm.HMAC_SHA_1,
baseString, oauth_consumer_secret + '&' + oauth_token_secret
)
);
if (!params['headers']) params['headers'] = {};
params['headers']['authorization'] = "OAuth " + Object.keys(oauthParameters)
.sort().reduce(function(acc, key) {
acc.push(key + '="' + _encode(oauthParameters[key]) + '"');
return acc;
}, []).join(', ');
params['payload'] = Object.keys(payload).reduce(function(acc, key) {
acc.push(key + '=' + _encode(payload[key]));
return acc;
}, []).join('&');
console.log(params)
response = UrlFetchApp.fetch(url, params);
for info...other than this, I also tried this repo - https://github.com/airhadoken/twitter-lib
still facing similar issues.
EDIT: on postman it works.. somethings wrong with the code then.. :(

Convert a JSON object to a particular CSV format

I want to convert this
{
"Name A":{
"Name B":{
"Name C":"Value C",
"Name D":"Value D",
"Name E":"Value E"
}
}
}
to this
Name A,,,
,Name B,,
,, Name C,Value C
,, Name D,Value D
,,Name E,Value E
It will look like this when opened in excel
I'm attempting to achieve this by running a small script, but before that I wanted to check if there is any node package or tool that can achieve this easily. Any clues?
Might be you can try with this npm module csvjson.
The link is here:- https://www.npmjs.com/package/csvjson
I solved this by writing my own script. I had to tweak a little to fit my necessity according to the format of the data. It is not the most elegant solution. It was quick dirty solution I got working. Still it's a good reference if anyone wants to try writing their own script to convert JSON to CSV
var fs = require('fs');
var file = 'templateEn.json';
var content = fs.readFileSync(file, { encoding: 'binary' });
var obj = JSON.parse(content);
var jsonString = ""
var lineEnd = "\r\n";
var firstLevelKeys = Object.keys(obj);
jsonString += firstLevelKeys[0] + ",,,,," + lineEnd;
var secondLevelKeys = Object.keys(obj["en"]);
secondLevelKeys.forEach(key => {
jsonString += ',' + key +',,,,'+ lineEnd
var thirdLevelKeys = Object.keys(obj["en"][key]);
thirdLevelKeys.forEach(key2=>{
if (typeof obj["en"][key][key2] === "string"){
jsonString += ",," + key2 + ',"' + obj["en"][key][key2]+'",,'+ lineEnd;
}
else if (typeof obj["en"][key][key2] === "object"){
var fourthLevelKeys = Object.keys(obj["en"][key][key2]);
jsonString += ',,' + key2 + ',,,' + lineEnd
fourthLevelKeys.forEach(key3 => {
if (typeof obj["en"][key][key2][key3] === "string") {
jsonString += ",,," + key3 + ',"' + obj["en"][key][key2][key3] + '",' + lineEnd;
}
else if (typeof obj["en"][key][key2][key3] === "object") {
var fifthLevelKeys = Object.keys(obj["en"][key][key2][key3]);
jsonString += ',,,' + key3 + ',,' + lineEnd
fifthLevelKeys.forEach(key4 => {
if (typeof obj["en"][key][key2][key3][key4] === "string") {
jsonString += ",,,," + key4 + ',"' + obj["en"][key][key2][key3][key4] + '"' + lineEnd;
}
})
}
})
}
});
});
fs.writeFileSync("generated.csv", jsonString, "utf8");

APNS - Node APN - payload - single quote

I am testing push notification using the node-apn module in node js. All works fine except that special characters such as a single quote does not properly escape. The push notification with a single quote (i.e. we'll becomes we\'ll in the actual notification). I tried regex, mongoose.toObject, message.message.toString() to no avail.
Message.findOne(query).lean().exec(function (err, doc){
if (!doc || doc == null) {
message.save(function(e) {
console.log("message saved")
if (e) {
console.log("there is an error")
console.log(e)
} else {
console.log(message.device_token)
var mesg = message.toObject();
var msg = JSON.stringify(mesg);
var payload = {
"contact" : message.contact,
"did" : message.did,
"id" : message.message_id,
"date" : message.date,
"message" : message.message
}
var clean = message.message.toString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
var note = new apns.Notification();
var myDevice = new apns.Device(message.device_token);
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.badge = 3;
note.alert = message.message;
note.payload = payload;
apnsConnection.pushNotification(note, myDevice);
}
})
}
});
});
I solved this issue by escaping the json formmatted single quote (which was //') in the message.message value using the replace function:
var messageStringCleaned = message.message.toString().replace(/\\/g,"");
var payload = {
"contact" : message.contact,
"did" : message.did,
"id" : message.message_id,
"date" : message.date,
"message" : messageStringCleaned
}
var note = new apns.Notification();
var myDevice = new apns.Device(message.device_token);
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.badge = 3;
note.alert = messageStringCleaned;
note.payload = payload;
apnsConnection.pushNotification(note, myDevice);

does gmaps support callback option

i want to get the cities list around a place defined with latitude and longitude
here is the simple code i use:
JSON.stringify = JSON.stringify || function (obj) {
var t = typeof (obj);
alert(t);
if (t != "object" || obj === null) {
if (t == "string") obj = '"'+obj+'"';
return String(obj);
} else {
var n, v, json = [], arr = (obj && obj.constructor == Array);
for (n in obj) {
v = obj[n]; t = typeof(v);
if (t == "string") v = '"'+v+'"';
else if (t == "object" && v !== null) v = JSON.stringify(v);
json.push((arr ? "" : '"' + n + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
}
function insertReply(content) {
var JSONString = JSON.stringify(content);
document.body.innerHTML += JSONString;
alert(JSONString);
}
var script = document.createElement('script');
var url = "http://maps.googleapis.com/maps/api/geocode/json?latlng=40.714224,-73.961452&sensor=false&callback=insertReply";
script.src = (url);
document.body.appendChild(script);
but it seems that google maps doesnt support the callback option (...&callback=insertReply)
is the a solution for that?
thank you in advance..
It's not supported.
Use the gecoding-service of the Maps-Javascript-API instead.

CRM 2011: Getting entity with Javascript

I am working on some CRM 2011 Online customisations and I need to get an entity using javascript.
The entity I need will be based on the ID value of another field (a Contact entity) - this Contact ID I can get fine.
The entity I want is a custom entity. There may be multiple matches based on the Contact ID so I just want to get the first one in the list (order not important)
So far I have looked into a few ways to do this...
OData - I couldn't find enough examples on this as to what query expressions I can create, also I don't know if/how to make this work for custom entities
FetchXML - I can create a nice FetchXML query using the built-in "advanced find" too and would be happy to call this from javascript if anyone can help? I found one promising answer here but I could not see how the "results" return data was being set (Service.Fetch function)
SOAP Request - First thing I tried is a similar method as I could have done in CRM 4 but this does not seem to work. Although the request executes, my result data just seems to be empty. This is all I have code for so if any one can spot a problem with the code below then that would be great.
EDIT: I have spotted some redundant query data (I had removed link opening tags but left closing tags) - since removing this I now get XML result data... however, the where clause does not seem to apply (just get list of all entities)
var xml = "<?xml version='1.0' encoding='utf-8'?>" +
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
GenerateAuthenticationHeader() +
"<soap:Body>" +
"<RetrieveMultiple xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
"<query xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +
"<q1:EntityName>new_vehicle</q1:EntityName>" +
"<q1:ColumnSet xsi:type='q1:ColumnSet'>" +
"<q1:Attributes>" +
"<q1:Attribute>new_vehicleid</q1:Attribute>" +
"<q1:Attribute>new_primarydriver</q1:Attribute>" +
"<q1:Attribute>statuscode</q1:Attribute>" +
"<q1:Attribute>new_registration</q1:Attribute>" +
"</q1:Attributes>" +
"</q1:ColumnSet>" +
"<q1:Distinct>false</q1:Distinct>" +
"<q1:Conditions>" +
"<q1:Condition>" +
"<q1:AttributeName>new_primarydriver</q1:AttributeName>" +
"<q1:Operator>Equal</q1:Operator>" +
"<q1:Values>" +
"<q1:Value xmlns:q2='http://microsoft.com/wsdl/types/' xsi:type='q2:guid'>" +
customerID +
"</q1:Value></q1:Values></q1:Condition>" +
"</q1:Conditions>" +
"</query></RetrieveMultiple>" +
"</soap:Body></soap:Envelope>";
var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);
var result = xmlHttpRequest.responseXML.xml;
var doc = new ActiveXObject("MSXML2.DOMDocument");
doc.async = false;
doc.loadXML(result);
var id = doc.selectSingleNode("//new_vehicleid");
var registration = doc.selectSingleNode("//new_registration");
if(id == null)
return null;
var vehicle = new Array();
value[0] = new Object();
value[0].id = id;
value[0].name = registration;
value[0].entityType = "new_vehicle";
return vehicle;
Sorry about the big code post but hopefully somebody who has a better understanding can help
Firstly, thanks to GlennFerrieLive for his answer post. The samples I found with the Dynamics CRM 2011 SDK (well just one in particular) really helped and the JSON parser included was perfect for the job!
I am posting this answer to give a full example of how I did it with some important comments to pay attention to which may not be so obvious from the SDK examples.
Get selected ID value from lookup field
The aim of my task was to use javascript to get set a lookup field, based on the selected data of another lookup entity. The entity to set is "new_vehicle" and the entity to query on is "customer".
First job is to get the ID value of the contact lookup field...
var customerItem = Xrm.Page.getAttribute("customerid").getValue();
var customerID = customerItem[0].id;
Querying an entity using an ID
Next is the part where I used the customerID value to find the vehicle that is currently assigned to them (the entity I want to use to set a lookup field).
First problem I found was that when querying with OData, the ID value does not seem to work with curly brackets {} - so these need to be removed...
customerID = customerID.replace('{', '').replace('}', '');
Next we get the oDataPath...
var oDataPath = Xrm.Page.context.getServerUrl() + "/xrmservices/2011/organizationdata.svc";
Then we can construct the OData query...
var filter = "/new_vehicleSet?" +
"$select=new_vehicleId,new_Registration" +
"&$filter=new_PrimaryDriver/Id eq (guid'" + customerID + "')" +
"&$orderby=new_LastAllocationDate desc" +
"&$top=1";
NOTE: There are a couple of important things to note here...
When using a guid value you must explicitly say it is a guid using (guid'xxx')
When filtering by a lookup entity (e.g. new_PrimaryDriver) you must append the value to query (e.g. Id) - this results in new_PrimaryDriver/Id
Once we have the query setup we can request the data as follows...
var retrieveRecordsReq = new XMLHttpRequest();
retrieveRecordsReq.open("GET", oDataPath + filter, true);
retrieveRecordsReq.setRequestHeader("Accept", "application/json");
retrieveRecordsReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
retrieveRecordsReq.onreadystatechange = function () {
if (this.readyState == 4) {
if (this.status == 200) {
var retrievedRecords = JSON.parse(retrieveRecordsReq.responseText).d;
if(retrievedRecords.results.length > 0)
{
var vehicle = retrievedRecords.results[0];
SetLookup("new_replacedvehicle", vehicle.new_vehicleId, vehicle.new_Registration, "new_vehicle");
}
}
}
};
retrieveRecordsReq.send();
Note that this is an asynchronous call and the onreadystatechange function will be processed upon completion, in this function we do a couple of checks to see if it was a success and the we parse the resulting JSON data - the JSON.Parse function has been included at the bottom of this post (but is available from the SDK)
Setting a lookup field using the entity queried above
The other function to make note of here is SetLookup which is just a simple helper function I added to set a lookup field. This is as follows...
function SetLookup(fieldName, idValue, textValue, typeValue)
{
var value = new Array();
value[0] = new Object();
value[0].id = idValue;
value[0].name = textValue;
value[0].typename = typeValue;
Xrm.Page.getAttribute(fieldName).setValue(value);
}
JSON parse function
This is the JSON helper function that was used in the above code (JSON.parse), pasted as it was found in the SDK...
if (!this.JSON) { this.JSON = {}; } (function () { function f(n) { return n < 10 ? '0' + n : n; } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function (key) { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null; }; String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) { return this.valueOf(); }; } var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\' }, rep; function quote(string) { escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { var i, k, v, length, mind = gap, partial, value = holder[key]; if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key); } if (typeof rep === 'function') { value = rep.call(holder, key, value); } switch (typeof value) { case 'string': return quote(value); case 'number': return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': return String(value); case 'object': if (!value) { return 'null'; } gap += indent; partial = []; if (Object.prototype.toString.apply(value) === '[object Array]') { length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { k = rep[i]; if (typeof k === 'string') { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { var i; gap = ''; indent = ''; if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } } else if (typeof space === 'string') { indent = space; } rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } return str('', { '': value }); }; } if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { var j; function walk(holder, key) { var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '#').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { j = eval('(' + text + ')'); return typeof reviver === 'function' ? walk({ '': j }, '') : j; } throw new SyntaxError('JSON.parse'); }; } } ());