Instructions:
Copy and paste 1) the function and 2) the helper function for recursion in client side JS or server side JS (google apps script)
Call the function and assign the output (a formatted string) to a variable or write the output directly to the console/log.
The function returns a formatted string with indents, visually appealing formatting, and no traditional js lint (no brackets or semi colons etc. ) it can be assigned to a variable or printed straight to the console.
1) var strX = ppJSONstringify_(blobX, blobXname, objLogConfig)
2) console.log ( ppJSONstringify_(blobX, blobXname, objLogConfig) )
Param1: blobX is obj, array, string >> objects and arrays get atypical formatting that is more ideal for logging and reading. strings pass through just like JSON.stringify
Param2: blobXname is for labeling the object , array or string
Param3: an optional object input for configuring the log output. It provides settings for properly organizing / sorting root and subroot keys, and for limiting row output on recordsets. it also provids tools for summarizing recordsets and objects.
Instructions for Testing and Learning (vary slightly for server side vs client side tesing:
Serverside - In addition to step 1 and 2 above -- 1) put copy/copies of objLogConfig in your code and 2) copies of sample object you can use for testing. Then execute the code in the GAS console and view the formatted output. Change log optiosn to see changes in output.
Client side - this is the same as server side -- but if you want to make a server call you need a server side handler and call back engine as well (which we provide for GAS)
var objLogConfig = {
// 1. VIP Sorter
sortPriority: ["meta", "objDoneX", "arrays", "doneX", "message", "extra1", "extra2"],
// 2. Row Limits
arrayRowLimits: {
"default_1D": 10,
"default_2D": 5,
"arrays.domains.arrRS": 3 // Force this to truncate early
},
// 3. View Overrides (Replaces legacy func2 and func3)
viewOverrides: {
"objDoneX.extra1": "summary",
"objDoneX.extra2": "array_of_objects_flatten"
}
}
return objLogConfig;
/*
var myLogConfig = {
sortPriority: [
"meta", "parsing", "security", "arrays","sqlTemplate", "select", "from", "where", "orderBy"
],
arrayRowLimits: {
"default_1D": 10,
"default_2D": 5,
"arrays.domains.arrRS": 3, // Explicit breadcrumb override
"arrays.ddTenants.arrRS": "all", // 'all' ignores the limit
"parsing.domains.arrWhereRules": 20
}
};
*/
}
function ss_ppJSONstringify_(blobX, blobXname, objLogConfig) {
var objConfig = {
sortPriority: [],
arrayRowLimits: { "default_1D": 10, "default_2D": 5 },
viewOverrides: {}
};
if (objLogConfig !== undefined && objLogConfig !== null) {
if (objLogConfig.sortPriority !== undefined) objConfig.sortPriority = objLogConfig.sortPriority;
if (objLogConfig.arrayRowLimits !== undefined) objConfig.arrayRowLimits = objLogConfig.arrayRowLimits;
if (objLogConfig.viewOverrides !== undefined) objConfig.viewOverrides = objLogConfig.viewOverrides;
}
var strFinalLog = blobXname;
var strTree = ss_ppJSONstringify_func1_build_tree_string_(blobX, 1, objConfig, "");
if (strTree !== "") {
if (strTree.substring(0, 1) === "[" || strTree.substring(0, 1) === '"') {
strFinalLog = strFinalLog + "\n " + strTree;
} else {
strFinalLog = strFinalLog + strTree;
}
}
// Pure string builder. No console.log!
return strFinalLog;
}
function ss_ppJSONstringify_func1_build_tree_string_(dataTemp, numLevel, objConfig, strCurrentPath) {
var strIndent = "";
for (var i = 0; i < numLevel; i = i + 1) {
strIndent = strIndent + " ";
}
if (dataTemp === null) {
return "null";
}
if (typeof dataTemp !== "object") {
if (typeof dataTemp === "string") {
return '"' + dataTemp + '"';
} else {
return dataTemp;
}
}
// ==========================================================
// VIEW OVERRIDE: SUMMARY (Replaces func2)
// ==========================================================
var strOverride = objConfig.viewOverrides[strCurrentPath];
if (strOverride === "summary") {
if (Array.isArray(dataTemp) === true) {
if (dataTemp.length === 0) {
return "[Array: 0 items]";
}
if (typeof dataTemp[0] === "object" && dataTemp[0] !== null) {
var numKeys = Object.keys(dataTemp[0]).length;
return "[Array: " + dataTemp.length + " items (Objects with " + numKeys + " keys)]";
}
return "[Array: " + dataTemp.length + " items]";
} else {
var arrSummaryKeys = Object.keys(dataTemp);
if (arrSummaryKeys.length === 0) {
return "[Object: 0 keys]";
}
var strKeyPreview = arrSummaryKeys.slice(0, 3).join(", ");
if (arrSummaryKeys.length > 3) {
strKeyPreview = strKeyPreview + "... +" + (arrSummaryKeys.length - 3) + " more";
}
return "[Object: " + arrSummaryKeys.length + " keys (" + strKeyPreview + ")]";
}
}
// ==========================================================
// HANDLE ARRAYS
// ==========================================================
if (Array.isArray(dataTemp) === true) {
if (dataTemp.length === 0) {
return "[ ]";
}
var is2D_y = "n";
var isObjArr_y = "n";
if (typeof dataTemp[0] === "object" && dataTemp[0] !== null) {
if (Array.isArray(dataTemp[0]) === true) {
is2D_y = "y";
} else {
isObjArr_y = "y";
}
}
// Determine row limit
var numLimit = "all";
var pathLimit = objConfig.arrayRowLimits[strCurrentPath];
if (pathLimit !== undefined) {
numLimit = pathLimit;
} else {
if (is2D_y === "y" || isObjArr_y === "y") {
if (objConfig.arrayRowLimits["default_2D"] !== undefined) {
numLimit = objConfig.arrayRowLimits["default_2D"];
} else {
numLimit = 5;
}
} else {
if (objConfig.arrayRowLimits["default_1D"] !== undefined) {
numLimit = objConfig.arrayRowLimits["default_1D"];
} else {
numLimit = 10;
}
}
}
var numTotalRows = dataTemp.length;
var numRowsToLoop = numTotalRows;
if (numLimit !== "all" && numTotalRows > numLimit) {
numRowsToLoop = numLimit;
}
var strArrayFinal = "";
// CHECK A: Array of Objects
if (isObjArr_y === "y") {
// VIEW OVERRIDE: FLATTEN (Replaces a_kv)
if (strOverride === "array_of_objects_flatten") {
for (var f = 0; f < numRowsToLoop; f = f + 1) {
var objFlatRow = dataTemp[f];
var arrFlatKeys = Object.keys(objFlatRow);
var arrPairs = [];
for (var p = 0; p < arrFlatKeys.length; p = p + 1) {
arrPairs.push(arrFlatKeys[p] + ": " + objFlatRow[arrFlatKeys[p]]);
}
strArrayFinal = strArrayFinal + "\n" + strIndent + " [" + f + "] " + arrPairs.join(" | ");
}
if (numRowsToLoop < numTotalRows) {
strArrayFinal = strArrayFinal + "\n" + strIndent + " ... + " + (numTotalRows - numRowsToLoop) + " more objects";
}
return strArrayFinal;
}
// Default: Vertical expansion
for (var a = 0; a < numRowsToLoop; a = a + 1) {
var strNestedObj = ss_ppJSONstringify_func1_build_tree_string_(dataTemp[a], numLevel + 1, objConfig, strCurrentPath + "[" + a + "]");
strArrayFinal = strArrayFinal + "\n" + strIndent + "[" + a + "]:" + strNestedObj;
}
if (numRowsToLoop < numTotalRows) {
strArrayFinal = strArrayFinal + "\n" + strIndent + "... + " + (numTotalRows - numRowsToLoop) + " more objects";
}
return strArrayFinal;
}
// CHECK B: 2D Spreadsheet Array
if (is2D_y === "y") {
for (var r = 0; r < numRowsToLoop; r = r + 1) {
strArrayFinal = strArrayFinal + "\n" + strIndent + " [ " + dataTemp[r].join(", ") + " ]";
}
if (numRowsToLoop < numTotalRows) {
strArrayFinal = strArrayFinal + "\n" + strIndent + " ... + " + (numTotalRows - numRowsToLoop) + " more rows";
}
return strArrayFinal;
}
// CHECK C: Standard 1D Array
var arr1DToPrint = [];
for (var x = 0; x < numRowsToLoop; x = x + 1) {
arr1DToPrint.push(dataTemp[x]);
}
var str1D = "[ " + arr1DToPrint.join(", ") + " ]";
if (numRowsToLoop < numTotalRows) {
str1D = str1D + " (+ " + (numTotalRows - numRowsToLoop) + " more items)";
}
return str1D;
}
// ==========================================================
// HANDLE OBJECTS (Folders)
// ==========================================================
if (typeof dataTemp === "object") {
var strObj = "";
var arrKeys = Object.keys(dataTemp);
if (objConfig.sortPriority.length > 0) {
arrKeys.sort(function(a, b) {
var indexA = objConfig.sortPriority.indexOf(a);
var indexB = objConfig.sortPriority.indexOf(b);
if (indexA !== -1 && indexB !== -1) return indexA - indexB;
if (indexA !== -1) return -1;
if (indexB !== -1) return 1;
return 0;
});
}
for (var k = 0; k < arrKeys.length; k = k + 1) {
var strKeyName = arrKeys[k];
var valTemp = dataTemp[strKeyName];
var strNextPath = "";
if (strCurrentPath === "") {
strNextPath = strKeyName;
} else {
strNextPath = strCurrentPath + "." + strKeyName;
}
if (typeof valTemp === "object" && valTemp !== null) {
if (Array.isArray(valTemp) === true) {
var valIs2D_y = "n";
var valIsObjArr_y = "n";
if (valTemp.length > 0) {
if (typeof valTemp[0] === "object" && valTemp[0] !== null) {
if (Array.isArray(valTemp[0]) === true) {
valIs2D_y = "y";
} else {
valIsObjArr_y = "y";
}
}
}
// Check if it's getting flattened, so we know whether to drop a line
var overrideChild_y = "n";
if (objConfig.viewOverrides[strNextPath] === "array_of_objects_flatten") {
overrideChild_y = "y";
}
if (valIs2D_y === "y" || valIsObjArr_y === "y") {
if (overrideChild_y === "y" || valIs2D_y === "y") {
// Drop a line for flattened objects and 2D arrays
var strNestedComplexLine = ss_ppJSONstringify_func1_build_tree_string_(valTemp, numLevel, objConfig, strNextPath);
strObj = strObj + "\n" + strIndent + strKeyName + ":" + strNestedComplexLine;
} else {
// Standard vertical objects get a slightly different drop
var strNestedComplex = ss_ppJSONstringify_func1_build_tree_string_(valTemp, numLevel, objConfig, strNextPath);
strObj = strObj + "\n" + strIndent + strKeyName + ":" + strNestedComplex;
}
} else {
// 1D Arrays or Summaries
var strNested1D = ss_ppJSONstringify_func1_build_tree_string_(valTemp, numLevel, objConfig, strNextPath);
strObj = strObj + "\n" + strIndent + strKeyName + ": " + strNested1D;
}
} else {
// It's a standard object/folder
var strCheckSummary = objConfig.viewOverrides[strNextPath];
if (strCheckSummary === "summary") {
// Keep summary inline
var strSummary = ss_ppJSONstringify_func1_build_tree_string_(valTemp, numLevel, objConfig, strNextPath);
strObj = strObj + "\n" + strIndent + strKeyName + ": " + strSummary;
} else {
// Drop a line for a nested folder
strObj = strObj + "\n" + strIndent + strKeyName;
var strNestedObj = ss_ppJSONstringify_func1_build_tree_string_(valTemp, numLevel + 1, objConfig, strNextPath);
strObj = strObj + strNestedObj;
}
}
} else {
var strPrimitiveValue = "";
if (typeof valTemp === "string") {
strPrimitiveValue = '"' + valTemp + '"';
} else {
strPrimitiveValue = valTemp;
}
strObj = strObj + "\n" + strIndent + strKeyName + ": " + strPrimitiveValue;
}
}
return strObj;
}
return "";
}