2023-09-19 03:16:35 -07:00
import type {
FieldType ,
IDataObject ,
IExecuteFunctions ,
INode ,
INodeExecutionData ,
} from 'n8n-workflow' ;
2023-12-05 02:17:08 -08:00
import {
deepCopy ,
NodeOperationError ,
jsonParse ,
validateFieldType ,
ApplicationError ,
} from 'n8n-workflow' ;
2023-09-19 03:16:35 -07:00
import set from 'lodash/set' ;
import get from 'lodash/get' ;
import unset from 'lodash/unset' ;
import { getResolvables } from '../../../../utils/utilities' ;
import type { SetNodeOptions , SetField } from './interfaces' ;
import { INCLUDE } from './interfaces' ;
const configureFieldHelper = ( dotNotation? : boolean ) = > {
if ( dotNotation !== false ) {
return {
set : ( item : IDataObject , key : string , value : IDataObject ) = > {
set ( item , key , value ) ;
} ,
get : ( item : IDataObject , key : string ) = > {
return get ( item , key ) ;
} ,
unset : ( item : IDataObject , key : string ) = > {
unset ( item , key ) ;
} ,
} ;
} else {
return {
set : ( item : IDataObject , key : string , value : IDataObject ) = > {
item [ key ] = value ;
} ,
get : ( item : IDataObject , key : string ) = > {
return item [ key ] ;
} ,
unset : ( item : IDataObject , key : string ) = > {
delete item [ key ] ;
} ,
} ;
}
} ;
export function composeReturnItem (
this : IExecuteFunctions ,
itemIndex : number ,
inputItem : INodeExecutionData ,
newFields : IDataObject ,
options : SetNodeOptions ,
) {
const newItem : INodeExecutionData = {
json : { } ,
2023-10-10 08:36:20 -07:00
pairedItem : { item : itemIndex } ,
2023-09-19 03:16:35 -07:00
} ;
if ( options . includeBinary && inputItem . binary !== undefined ) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
newItem . binary = { } ;
Object . assign ( newItem . binary , inputItem . binary ) ;
}
const fieldHelper = configureFieldHelper ( options . dotNotation ) ;
switch ( options . include ) {
case INCLUDE . ALL :
newItem . json = deepCopy ( inputItem . json ) ;
break ;
case INCLUDE . SELECTED :
const includeFields = ( this . getNodeParameter ( 'includeFields' , itemIndex ) as string )
. split ( ',' )
. map ( ( item ) = > item . trim ( ) )
. filter ( ( item ) = > item ) ;
for ( const key of includeFields ) {
const fieldValue = fieldHelper . get ( inputItem . json , key ) as IDataObject ;
let keyToSet = key ;
if ( options . dotNotation !== false && key . includes ( '.' ) ) {
keyToSet = key . split ( '.' ) . pop ( ) as string ;
}
fieldHelper . set ( newItem . json , keyToSet , fieldValue ) ;
}
break ;
case INCLUDE . EXCEPT :
const excludeFields = ( this . getNodeParameter ( 'excludeFields' , itemIndex ) as string )
. split ( ',' )
. map ( ( item ) = > item . trim ( ) )
. filter ( ( item ) = > item ) ;
const inputData = deepCopy ( inputItem . json ) ;
for ( const key of excludeFields ) {
fieldHelper . unset ( inputData , key ) ;
}
newItem . json = inputData ;
break ;
case INCLUDE . NONE :
break ;
default :
2023-12-05 02:17:08 -08:00
throw new ApplicationError ( ` The include option " ${ options . include } " is not known! ` , {
level : 'warning' ,
} ) ;
2023-09-19 03:16:35 -07:00
}
for ( const key of Object . keys ( newFields ) ) {
fieldHelper . set ( newItem . json , key , newFields [ key ] as IDataObject ) ;
}
return newItem ;
}
export const parseJsonParameter = (
jsonData : string | IDataObject ,
node : INode ,
i : number ,
entryName? : string ,
) = > {
let returnData : IDataObject ;
const location = entryName ? ` entry " ${ entryName } " inside 'Fields to Set' ` : "'JSON Output'" ;
if ( typeof jsonData === 'string' ) {
try {
returnData = jsonParse < IDataObject > ( jsonData ) ;
} catch ( error ) {
let recoveredData = '' ;
try {
recoveredData = jsonData
. replace ( /'/g , '"' ) // Replace single quotes with double quotes
. replace ( /(['"])?([a-zA-Z0-9_]+)(['"])?:/g , '"$2":' ) // Wrap keys in double quotes
. replace ( /,\s*([\]}])/g , '$1' ) // Remove trailing commas from objects
. replace ( /,+$/ , '' ) ; // Remove trailing comma
returnData = jsonParse < IDataObject > ( recoveredData ) ;
} catch ( err ) {
const description =
recoveredData === jsonData ? jsonData : ` ${ recoveredData } ; \ n Original input: ${ jsonData } ` ;
throw new NodeOperationError ( node , ` The ${ location } in item ${ i } contains invalid JSON ` , {
description ,
} ) ;
}
}
} else {
returnData = jsonData ;
}
if ( returnData === undefined || typeof returnData !== 'object' || Array . isArray ( returnData ) ) {
throw new NodeOperationError (
node ,
` The ${ location } in item ${ i } does not contain a valid JSON object ` ,
) ;
}
return returnData ;
} ;
export const validateEntry = (
entry : SetField ,
node : INode ,
itemIndex : number ,
ignoreErrors = false ,
2023-10-03 01:18:33 -07:00
nodeVersion? : number ,
2023-09-19 03:16:35 -07:00
) = > {
let entryValue = entry [ entry . type ] ;
const name = entry . name ;
2023-10-12 08:07:12 -07:00
if ( nodeVersion && nodeVersion >= 3.2 && ( entryValue === undefined || entryValue === null ) ) {
return { name , value : null } ;
}
2023-09-19 03:16:35 -07:00
const entryType = entry . type . replace ( 'Value' , '' ) as FieldType ;
2023-10-03 01:18:33 -07:00
const description = ` To fix the error try to change the type for the field " ${ name } " or activate the option “Ignore Type Conversion Errors” to apply a less strict type validation ` ;
2023-09-19 03:16:35 -07:00
if ( entryType === 'string' ) {
2023-10-03 01:18:33 -07:00
if ( nodeVersion && nodeVersion > 3 && ( entryValue === undefined || entryValue === null ) ) {
if ( ignoreErrors ) {
return { name , value : null } ;
} else {
throw new NodeOperationError (
node ,
` ' ${ name } ' expects a ${ entryType } but we got ' ${ String ( entryValue ) } ' [item ${ itemIndex } ] ` ,
{ description } ,
) ;
}
} else if ( typeof entryValue === 'object' ) {
2023-09-19 03:16:35 -07:00
entryValue = JSON . stringify ( entryValue ) ;
} else {
entryValue = String ( entryValue ) ;
}
}
const validationResult = validateFieldType ( name , entryValue , entryType ) ;
if ( ! validationResult . valid ) {
if ( ignoreErrors ) {
validationResult . newValue = entry [ entry . type ] ;
} else {
const message = ` ${ validationResult . errorMessage } [item ${ itemIndex } ] ` ;
throw new NodeOperationError ( node , message , {
itemIndex ,
description ,
} ) ;
}
}
const value = validationResult . newValue === undefined ? null : validationResult . newValue ;
return { name , value } ;
} ;
export function resolveRawData ( this : IExecuteFunctions , rawData : string , i : number ) {
const resolvables = getResolvables ( rawData ) ;
let returnData : string = rawData ;
if ( resolvables . length ) {
for ( const resolvable of resolvables ) {
const resolvedValue = this . evaluateExpression ( ` ${ resolvable } ` , i ) ;
if ( typeof resolvedValue === 'object' && resolvedValue !== null ) {
returnData = returnData . replace ( resolvable , JSON . stringify ( resolvedValue ) ) ;
} else {
returnData = returnData . replace ( resolvable , resolvedValue as string ) ;
}
}
}
return returnData ;
}