Last active
July 14, 2025 19:37
-
-
Save smhmic/e2ee569c843b0350be524764f41cbe18 to your computer and use it in GitHub Desktop.
GAPPs GSheet build data layer push code snippet from list of key/values
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Generates a JSON or dataLayer.push() snippet per row based on provided keys, values, comments, and an optional filter. | |
* | |
* Accepts array inputs and returns one dataLayer code block per row. | |
* Designed to be used with ARRAYFORMULA or range-based inputs. | |
* | |
* @customfunction | |
* @param {Range} keys A range of keys. Each row represents one variable's key path (e.g. ["ecommerce", "items", "price"]). | |
* @param {Range} values A column of values. Each cell corresponds to the value of the key in the same row of the `keys` range. | |
* @param {Range} comments Optional. A column of developer comments or descriptions per key. | |
* @param {Range} filter Optional. A column of filter groups. For each output row, only includes key/value rows with the same filter value. | |
* @return {String[][]} An array of formatted dataLayer.push() blocks, one per row. | |
* @example | |
* =MY_DATALAYER_CODE_AF(A2:A10, B2:B10, C2:C10, D2:D10) | |
*/ | |
function MY_DATALAYER_CODE_AF( keys, values, comments, filter ){ | |
const args = [].slice.apply(arguments); | |
return args[0].map( ( _, idx ) =>{ | |
return [ MY_DATALAYER_CODE( keys, values, comments, filter && /\bSchema\b/.exec(filter[idx]), filter, filter[idx] ) ]; | |
}); | |
} | |
function _customFormulaError( msg ){ | |
return "Error: "+msg; | |
} | |
function _foreach( arrOrObj, callback ){ | |
if( arrOrObj.join ) | |
return arrOrObj.forEach( ( v, k )=> callback( v, k, { isArray: true, length: arrOrObj.length, index: k } ) ); | |
let idx = 0; | |
Object.entries(arrOrObj).forEach( ([ k, v ])=>{ | |
callback( v, k, { isArray: false, length: Object.keys(arrOrObj).length, index: idx++ } ); | |
}); | |
} | |
function MY_DATALAYER_CODE( keys, values, comments, jsonOnly, filter, filterCondition ){ | |
let //model = {}, | |
output ='', | |
args = [].slice.call(arguments), keyPath; | |
const log = function( str ){ output += "\n > "+str+"\n-----"} ; | |
const model = {}, total = args[0].length; | |
let listArgs = [... args.slice(0,3),... args.slice(4,5)]; | |
if( !listArgs[0] || !listArgs[0].join ) return _customFormulaError( 'Input must be an array' ); | |
if( !total ) return _customFormulaError( '(empty)' ); | |
for( let i=1; i<listArgs.length; i++ ) // create empty arrays for unused optional inputs | |
listArgs[i] = listArgs[i] || [...Array(total)].map(e => Array(1)); | |
if( ! listArgs.every( arg => arg.length === total ) ) | |
return _customFormulaError( 'Inputs must be same length [' +listArgs.map( arg => arg.length).join(",")+']' ); | |
[ keys, values, comments, filter ] = listArgs; | |
keys.forEach( ( keyPath, idx ) => { | |
let val = values ? values[idx][0] : "?", | |
comment = comments[idx][0]; | |
let _model = model; | |
keyPath = keys[idx].filter( k => !!k ); | |
if( !keyPath || ( filter[idx][0] != filterCondition ) ) return; | |
keyPath = keyPath.join ? keyPath : [keyPath]; // Convert singular key to a key path array | |
//TODO enforce order (i.e. 'event' key first) | |
keyPath.forEach( (k,level) => { | |
//if( !k ) return; //handled above by .filter( k => !!k ) | |
// TODO handle merging if already exists // if( _model[k]?.constructor === Object ) | |
if( level < keyPath.length-1 ) | |
//return ( _model = ( _model[k] = _model[k] || {} )); | |
return ( _model = ( _model[k] = ( _model[k]?.constructor === Object ) ? _model[k] : {} )); | |
return ( _model[k] = _model[k] && (_model[k]!=="Object") &&(_model[k]?.val!=="Object") ? _model[k] : ( comment ? { value: val, _dlcode_col_comment: comment } : val ) ); | |
}); | |
}); | |
var _stringifyModel = function( obj, depth ){ // TODO optimize | |
let formatKey = k => (k=k+'').match(/^[a-zA-Z0-9_]+$/) ? k : "'"+k+"'"; | |
let r = [], comment, | |
left = obj.join ? '[' : '{', right = obj.join ? ']' : '}'; | |
newline = '\n'+''.repeat((depth||0)*2); | |
try{ | |
if( typeof obj === "undefined" ){ return "(UNDEFINED)"; }; | |
if( typeof obj !== "object" || obj === null ) | |
return ( ["String","Number","Boolean","Date","Object","Array"].includes(obj) | |
? obj | |
: JSON.stringify( (()=>{ try{ return JSON.parse( obj ) || obj; }catch(ex){} return obj })() ) ); | |
// TODO if all obj keys are numeric, parse as array - obj.values() | |
_foreach( obj, ( v, k, { isArray, length, index } )=>{ | |
// r.push( `_foreach (${index}/${length}) [${k}] : ${v} isArray:${isArray} : ${v}` ); | |
if( 'undefined' === typeof v ) return; | |
if( comment = ( v && v._dlcode_col_comment ) ) v = v.value; | |
r.push( | |
(isArray | |
? _stringifyModel( v, (depth||0)+1 ) | |
: formatKey(k)+' : '+_stringifyModel( v, (depth||0)+1 ) ) | |
+( index === length-1 ? '' : ',' ) | |
+( comment ? ' // '+comment : '' ) | |
); | |
}); | |
switch( r.length ){ | |
case 0: r = left+right; break; | |
case 1: | |
case 2: if( jsonOnly ){ | |
r = left+' '+r.join(' ')+' '+right; break; } | |
default: r = left+newline+' '+r.join(''+newline+' ')+newline+right; break; | |
} | |
return r; | |
}catch(ex){ r.push( ex+'' ); return r.join("\n"); } | |
}; | |
output += _stringifyModel( model ); | |
return jsonOnly ? output : '(window.dataLayer=window.dataLayer||[]).push('+output+");"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment