NSJSONSerialization with nested NSMutableDictionary - nested

I got something here I need to have another set of eyes look over. I've got an NSMutableDictionary that I need to convert to a JSON object and then send out to my server. The dictionary looks like this...
{
data = (
{
184 = 3;
185 = "";
186 = "";
187 = "";
188 = "";
latitude = "";
longitude = "";
recorded = "2012-06-19 12:53:16 +0000";
}, {
184 = 0;
185 = Is;
186 = This;
187 = "Working?";
188 = "I think so.";
latitude = "";
longitude = "";
recorded = "2012-06-19 12:54:26 +0000";
}
);
deviceID = b1c96c4467a8bcca97a826ad9941a10a;
key = 6puwX3v2;
method = putSurveyData;
responses = 2;
surveyID = 84;
}
So far, no problems, but as soon as I go to convert that dictionary to a JSON object using NSJSONSerialization it crashes my app. The above dictionary is called mainDictionary and I use the following line to convert to JSON.
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:mainDicitionary options:kNilOptions error:nil];
If I remove the nested dictionaries from the 'data' key, it works fine. I have even tried to put a common array in the 'data' key, and that works also. It just seems to bomb out as soon as I stick an NSMutableDictionary in there. Is there something simple i'm missing?
Thanks!

I found the answer. The "recorded" field above was actually an [NSDate date] rather than a string representation of the date stamp. I cast it to a string and all is fine.

Related

Getting NaN when summing values in for loop

I want to sum the values from an API response. The values are stored in JSON format. My code looks like this:
var returnCode;
var getUrl = "url";
returnCode = httpGet(getUrl);
var objekt = JSON.parse(returnCode);
function httpGet(url){
var response = requestSync(
'GET',
url
);
return response.body;
}
var price = 0;
for(i=0; i<30; i++)
{
price = price + objekt.data.sales[i].price;
}
price /= 30;
console.log(price); //displays NaN
console.log(objekt.data.sales[1].price); //displays correct price of one element
The problem is that when I try to output price it returns NaN, but if i just display a value of a single element it works fine. Any ideas why it does that? there are 30 elements in the array BTW.
Actually Json values are coming as string
Change the for loop
for(i=0; i<30; i++)
{
price = price + parseInt(objekt.data.sales[i].price);
}

How to manipulate a string representing a raw number (e.g. 130000.1293) into a formatted string (e.g. 130,000.13)?

In apps script I want to obtain formatted 'number' strings. The input is an unformatted number. With an earlier answer posted by #slandau, I thought I had found a solution by modifying his code (see code snippet). It works in codepen, but not when I am using apps script.
1. Does anyone know what went wrong here?
2. I noticed this code works except when entering a number ending in .0, in that case the return value is also .0 but should be .00. I would like some help fixing that too.
Thanks!
I have tried to look for type coercion issues, but wasn't able to get it down. I am fairly new to coding.
function commaFormatted(amount)
{
var delimiter = ","; // replace comma if desired
var a = amount.split('.', 2);
var preD = a[1]/(Math.pow(10,a[1].length-2));
var d = Math.round(preD);
var i = parseInt(a[0]);
if(isNaN(i)) { return ''; }
var minus = '';
if(i < 0) { minus = '-'; }
i = Math.abs(i);
var n = new String(i);
var a = [];
while(n.length > 3)
{
var nn = n.substr(n.length-3);
a.unshift(nn);
n = n.substr(0,n.length-3);
}
if(n.length > 0) { a.unshift(n); }
n = a.join(delimiter);
if(d.length < 1) { amount = n; }
else { amount = n + '.' + d; }
amount = minus + amount;
return amount;
}
console.log(commaFormatted('100000.3532'))
The expected result would be 100,000.35.
I am getting this in the IDE of codepen, but in GAS IDE is stops at the .split() method => not a function. When converting var a to a string = I am not getting ["100000", "3532"] when logging var a. Instead I am getting 100000 and was expecting 3532.
Based on this answer, your function can be rewritten to
function commaFormatted(amount)
{
var inputAmount;
if (typeof(amount) == 'string') {
inputAmount = amount;
} else if (typeof(amount) == 'float') {
inputAmount = amount.toString();
}
//--- we expect the input amount is a String
// to make is easier, round the decimal part first
var roundedAmount = parseFloat(amount).toFixed(2);
//--- now split it and add the commas
var parts = roundedAmount.split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}
console.log(commaFormatted(100000.3532));
console.log(commaFormatted('1234567.3532'));

How can I create a DataField that calculates values in EPPlus?

I am porting Excel Interop PivotTable code to EPPlus. I'm at a sticking point in generating a calculated field value. In Excel Interop I can do it this way:
pvt.AddDataField(pvt.PivotFields("TotalQty"), "Total Packages", XlConsolidationFunction.xlSum).NumberFormat = "###,##0";
pvt.AddDataField(pvt.PivotFields("TotalPrice"), "Total Purchases", XlConsolidationFunction.xlSum).NumberFormat = "$#,##0.00";
PivotField avg = pvt.CalculatedFields().Add("Average Price", "=TotalPrice/TotalQty", true);
avg.Orientation = XlPivotFieldOrientation.xlDataField;
avg.NumberFormat = "$###0.00";
With this the "Average Price" value on the PivotTable displays the value for TotalPrice divided by TotalQty.
But how to do it in EPPlus? I can create the "plain vanilla" data fields like so:
var totQtyField = pivotTable.Fields["TotalQty"];
pivotTable.DataFields.Add(totQtyField);
var totPriceField = pivotTable.Fields["TotalPrice"];
pivotTable.DataFields.Add(totPriceField);
...but when it comes to calculating a value, I'm baffled. My starting point was an online example for sums like so:
pivotTable.DataFields.Add(pivotTable.Fields["AvgPrice"]).Function =
OfficeOpenXml.Table.PivotTable.DataFieldFunctions.Sum();
So I tried the following, but none of them is right:
pivotTable.DataFields.Add(pivotTable.Fields["AvgPrice"]).Function =
OfficeOpenXml.Table.PivotTable.DataFieldFunctions.Average(totPriceField, totQtyField);
pivotTable.DataFields.Add(pivotTable.Fields["AvgPrice"]).Function = "TotalPrice/TotalQty";
pivotTable.DataFields.Add(pivotTable.Fields["AvgPrice"]) = "TotalPrice/TotalQty";
pivotTable.DataFields.Add(pivotTable.Fields["AvgPrice"]) = totPriceField / totQtyField;
pivotTable.DataFields.Add(pivotTable.Fields["AvgPrice"]).Function =
OfficeOpenXml.Table.PivotTable.
DataFieldFunctions.Product(totPriceField/totQtyField);
None of those flailings even compile. What adjustment do I need to make, or what completely new approach do I need to take?
UPDATE
I could calculate the values and put them on the data sheet and reference it that way; but is this really the only/best way to do this?
UPDATE 2
I did that (added the vals directly to the sheet that contains the source data for the Pivot Table):
var avgCell = rawDataWorksheet.Cells[_lastRowAddedRawData + 1, 9];
if ((TotalPrice < 0.0M) || (Quantity < 1))
{
avgCell.Value = 0.0;
}
else
{
avgCell.Value = TotalPrice / Quantity;
}
var prcntgCell = rawDataWorksheet.Cells[_lastRowAddedRawData + 1, 10];
if ((TotalPrice < 0.0M) || (MonthYear == String.Empty))
{
prcntgCell.Value = 0.0;
}
else
{
prcntgCell.Value = GetPercentageOfItemForMonthYear(TotalPrice, MonthYear);
}
private double GetPercentageOfItemForMonthYear(decimal totPrice, strin
monthYear)
{
decimal totalForMonthYear = monthlySales[monthYear];
double prcntg = GetPercentageOfItem(totPrice, totalForMonthYear);
return prcntg;
}
private double GetPercentageOfItem(decimal portionOfTotal, decimal
grandTotal)
{
if ((portionOfTotal <= 0.0M) || (grandTotal <= 0.0M))
{
return 0.0;
}
if (portionOfTotal == grandTotal)
{
return 100.0;
}
double d = Convert.ToDouble(portionOfTotal)
/ Convert.ToDouble(grandTotal) * 100;
return Math.Round(d, 2);
}
...but still would like to know how that's accomplishable using calculated fields while the PivotTable is being created.
EPPlus does not currently support calculated fields in PivotTables. I used the following workaround:
using System.Linq;
using System.Xml;
using OfficeOpenXml.Table.PivotTable;
using ReflectionMagic;
public static ExcelPivotTableField AddCalculatedField(this ExcelPivotTable pivotTable, string calcFieldName, string formula)
{
var dynamicPivotTable = pivotTable.AsDynamic();
var maxIndex = pivotTable.Fields.Max(f => f.Index);
const string schemaMain = #"http://schemas.openxmlformats.org/spreadsheetml/2006/main";
var cacheTopNode = dynamicPivotTable.CacheDefinition.CacheDefinitionXml.SelectSingleNode("//d:cacheFields", dynamicPivotTable.NameSpaceManager);
cacheTopNode.SetAttribute("count", (int.Parse(cacheTopNode.GetAttribute("count")) + 1).ToString());
var cacheFieldNode = dynamicPivotTable.CacheDefinition.CacheDefinitionXml.CreateElement("cacheField", schemaMain);
cacheFieldNode.SetAttribute("name", calcFieldName);
cacheFieldNode.SetAttribute("databaseField", "0");
cacheFieldNode.SetAttribute("formula", formula);
cacheFieldNode.SetAttribute("numFmtId", "0");
cacheTopNode.AppendChild(cacheFieldNode);
var topNode = dynamicPivotTable.PivotTableXml.SelectSingleNode("//d:pivotFields", dynamicPivotTable.NameSpaceManager);
topNode.SetAttribute("count", (int.Parse(topNode.GetAttribute("count")) + 1).ToString());
XmlElement fieldNode = dynamicPivotTable.PivotTableXml.CreateElement("pivotField", schemaMain);
fieldNode.SetAttribute("compact", "0");
fieldNode.SetAttribute("outline", "0");
fieldNode.SetAttribute("showAll", "0");
fieldNode.SetAttribute("defaultSubtotal", "0");
topNode.AppendChild(fieldNode);
var excelPivotTableFieldType = typeof(ExcelPivotTableField).AsDynamicType();
var excelPivotTableField = excelPivotTableFieldType.New((XmlNamespaceManager)dynamicPivotTable.NameSpaceManager, fieldNode, (ExcelPivotTable)dynamicPivotTable, maxIndex + 1, maxIndex + 1);
excelPivotTableField.SetCacheFieldNode(cacheFieldNode);
dynamicPivotTable.Fields.AddInternal(excelPivotTableField);
return pivotTable.Fields.First(f => f.Name == calcFieldName);
}
Example usage:
var calc1 = pivotTable.AddCalculatedField("Calc1", "BCol*BCol");
var dataField = pivotTable.DataFields.Add(calc1);
dataField.Function = DataFieldFunctions.Sum;
dataField.Name = "Override Name";

How to get over 1000 records from a SuiteScript Saved Search?

Below is code I came up with to run a Saved Search in NetSuite using SuiteScript, create a CSV with the Saved Search results and then email the CSV. The trouble is, the results are limited to 1000 records. I've researched this issue and it appears the solution is to run a loop that slices in increments of 1000. A sample of what I believe is used to slice searches is also below.
However, I cannot seem to be able to incorporate the slicing into my code. Can anyone help me combine the slicing code with my original search code?
var search = nlapiSearchRecord('item', 'customsearch219729');
// Creating some array's that will be populated from the saved search results
var content = new Array();
var cells = new Array();
var temp = new Array();
var x = 0;
// Looping through the search Results
for (var i = 0; i < search.length; i++) {
var resultSet = search[i];
// Returns an array of column internal Ids
var columns = resultSet.getAllColumns();
// Looping through each column and assign it to the temp array
for (var y = 0; y <= columns.length; y++) {
temp[y] = resultSet.getValue(columns[y]);
}
// Taking the content of the temp array and assigning it to the Content Array.
content[x] += temp;
// Incrementing the index of the content array
x++;
}
//Inserting headers
content.splice(0, 0, "sku,qty,");
// Creating a string variable that will be used as the CSV Content
var contents;
// Looping through the content array and assigning it to the contents string variable.
for (var z = 0; z < content.length; z++) {
contents += content[z].replace('undefined', '') + '\n';
}
// Creating a csv file and passing the contents string variable.
var file = nlapiCreateFile('InventoryUpdate.csv', 'CSV', contents.replace('undefined', ''));
// Emailing the script.
function SendSSEmail()
{
nlapiSendEmail(768, 5, 'Inventory Update', 'Sending saved search via scheduled script', 'cc#email.com', null, null, file, true, null, 'cc#email.com');
}
The following code is an example of what I found that is used to return more than a 1000 records. Again, as a novice, I can't seem to incorporate the slicing into my original, functioning SuiteScript. Any help is of course greatly appreciated.
var filters = [...];
var columns = [...];
var results = [];
var savedsearch = nlapiCreateSearch( 'customrecord_mybigfatlist', filters, columns );
var resultset = savedsearch.runSearch();
var searchid = 0;
do {
var resultslice = resultset.getResults( searchid, searchid+1000 );
for (var rs in resultslice) {
results.push( resultslice[rs] );
searchid++;
}
} while (resultslice.length >= 1000);
return results;
Try out this one :
function returnCSVFile(){
function escapeCSV(val){
if(!val) return '';
if(!(/[",\s]/).test(val)) return val;
val = val.replace(/"/g, '""');
return '"'+ val + '"';
}
function makeHeader(firstLine){
var cols = firstLine.getAllColumns();
var hdr = [];
cols.forEach(function(c){
var lbl = c.getLabel(); // column must have a custom label to be included.
if(lbl){
hdr.push(escapeCSV(lbl));
}
});
return hdr.join(",");
}
function makeLine(srchRow){
var cols = srchRow.getAllColumns();
var line = [];
cols.forEach(function(c){
if(c.getLabel()){
line.push(escapeCSV(srchRow.getText(c) || srchRow.getValue(c)));
}
});
return line.join(",");
}
function getDLFileName(prefix){
function pad(v){ if(v >= 10) return v; return "0"+v;}
var now = new Date();
return prefix + '-'+ now.getFullYear() + pad(now.getMonth()+1)+ pad(now.getDate()) + pad( now.getHours()) +pad(now.getMinutes()) + ".csv";
}
var srchRows = getItems('item', 'customsearch219729'); //function that returns your saved search results
if(!srchRows) throw nlapiCreateError("SRCH_RESULT", "No results from search");
var fileLines = [makeHeader(srchRows[0])];
srchRows.forEach(function(soLine){
fileLines.push(makeLine(soLine));
});
var file = nlapiCreateFile('InventoryUpdate.csv', 'CSV', fileLines.join('\r\n'));
nlapiSendEmail(768, 5, 'Test csv Mail','csv', null, null, null, file);
}
function getItems(recordType, searchId) {
var savedSearch = nlapiLoadSearch(recordType, searchId);
var resultset = savedSearch.runSearch();
var returnSearchResults = [];
var searchid = 0;
do {
var resultslice = resultset.getResults(searchid, searchid + 1000);
for ( var rs in resultslice) {
returnSearchResults.push(resultslice[rs]);
searchid++;
}
} while (resultslice.length >= 1000);
return returnSearchResults;
}
I looked into your code but it seems you're missing the label headers in the generated CSV file. If you are bound to use your existing code then just replace
var search = nlapiSearchRecord('item', 'customsearch219729');
with
var search = getItems('item', 'customsearch219729');
and just use the mentioned helper function to get rid off the 1000 result limit.
Cheers!
I appreciate it has been a while since this was posted and replied to but for others looking for a more generic response to the original question the following code should suffice:
var search = nlapiLoadSearch('record_type', 'savedsearch_id');
var searchresults = search.runSearch();
var resultIndex = 0;
var resultStep = 1000;
var resultSet;
do {
resultSet = searchresults.getResults(resultIndex, resultIndex + resultStep); // retrieves all possible results up to the 1000 max returned
resultIndex = resultIndex + resultStep; // increment the starting point for the next batch of records
for(var i = 0; !!resultSet && i < resultSet.length; i++){ // loop through the search results
// Your code goes here to work on a the current resultSet (upto 1000 records per pass)
}
} while (resultSet.length > 0)
Also worth mentioning, if your code is going to be updating fields / records / creating records you need to bear in mind script governance.
Moving your code to a scheduled script to process large volumes of records is more efficient and allows you to handle governance.
The following line:
var savedsearch = nlapiCreateSearch( 'customrecord_mybigfatlist', filters, columns );
can be adapted to your own saved search like this:
var savedsearch = nlapiLoadSearch('item', 'customsearch219729');
Hope this helps.

ActionScript 3: ByteArray to binary String

I've been asked to implement and MD5 hasher ActionScript-3 and as I was in the middle of debugging how I formatted my input I came across a problem. When I try and output the ByteArray as a binary string using .toString(2), the toString(2) method will perform some short cuts that alter how the binary should look.
For Example
var bytes:ByteArray = new ByteArray();
bytes.endian = Endian.LITTLE_ENDIAN;
bytes.writeUTFBytes("a");
bytes.writeByte(0x0);
var t1:String = bytes[0].toString(2); // is 1100001 when it should be 01100001
var t2:String = bytes[1].toString(2); // is 0 when it should be 00000000
so I guess my question is, might there a way to output a binary String from a ByteArray that will always shows each byte as a 8 bit block?
All you need is to pad the output of toString(2) with zeros on the left to make its length equal to 8. Use this function for padding
function padString(str:String, len:int, char:String, padLeft:Boolean = true):String{
var padLength:int = len - str.length;
var str_padding:String = "";
if(padLength > 0 && char.length == 1)
for(var i:int = 0; i < padLength; i++)
str_padding += char;
return (padLeft ? str_padding : "") + str + (!padLeft ? str_padding: "");
}
With this function the code looks like this and gives the correct output
var bytes:ByteArray = new ByteArray();
bytes.endian = Endian.LITTLE_ENDIAN;
bytes.writeUTFBytes("a");
bytes.writeByte(0x0);
var t1:String = padString(bytes[0].toString(2), 8, "0"); // is now 01100001
var t2:String = padString(bytes[1].toString(2), 8, "0"); // is now 00000000
Update
If you want to get a string representation of complete byteArray you can use a function which iterates on the byteArray. I have wrote the following function and it seems to work correctly. Give it a try
// String Padding function
function padString(str:String, len:int, char:String, padLeft:Boolean = true):String{
// get no of padding characters needed
var padLength:int = len - str.length;
// padding string
var str_padding:String = "";
// loop from 0 to no of padding characters needed
// Note: this loop will not run if padLength is less than 1
// as i < padLength will be false from begining
for(var i:int = 0; i < padLength; i++)
str_padding += char;
// return string with padding attached either to left or right depending on the padLeft Boolean
return (padLeft ? str_padding : "") + str + (!padLeft ? str_padding: "");
}
// Return a Binary String Representation of a byte Array
function byteArrayToBinaryString(bArray:ByteArray):String{
// binary string to return
var str:String = "";
// store length so that it is not recomputed on every loop
var aLen = bArray.length;
// loop over all available bytes and concatenate the padded string to return string
for(var i:int = 0; i < aLen; i++)
str += padString(bArray[i].toString(2), 8, "0");
// return binary string
return str;
}
Now you can simply use the byteArrayToBinaryString() function like this:
// init byte array and set Endianness
var bytes:ByteArray = new ByteArray();
bytes.endian = Endian.LITTLE_ENDIAN;
// write some data to byte array
bytes.writeUTFBytes("a");
bytes.writeByte(0x0);
// convert to binaryString
var byteStr:String = byteArrayToBinaryString(bytes); // returns 0110000100000000
Here is a function extended on the Hurlant library to handle hashing byteArray.
This class has a learning curve but once you get it you will love it.
As far as your ByteArray issue with toString. I know the toString method is not accurate For this very reason.
You might want to look into byteArray.readMultiByte that will give you the 01 you are looking for. Although I can't seem top get it to work on my sample code either lol
I just always get a and empty string.
var bytes:ByteArray = new ByteArray();
bytes.endian = Endian.LITTLE_ENDIAN;
bytes.writeUTFBytes("a");
bytes.writeByte(0x0);
bytes.position = 0
var t1:String = bytes.readMultiByte(1,'us-ascii'); // is 1100001 when it should be 01100001
trace(t1)
var t2:String = bytes.readMultiByte(1,'iso-8859-01'); // is 0 when it should be 00000000
trace(t2)

Resources