Skip to content

Instantly share code, notes, and snippets.

@smhmic
Last active July 14, 2025 19:37
Show Gist options
  • Save smhmic/e2ee569c843b0350be524764f41cbe18 to your computer and use it in GitHub Desktop.
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
/**
* 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