2019-10-31 09:24:56 -07:00
import {
get ,
set ,
unset ,
} from 'lodash' ;
2021-03-25 04:58:54 -07:00
import {
BINARY_ENCODING ,
} from 'n8n-core' ;
2019-10-31 09:24:56 -07:00
import { IExecuteFunctions } from 'n8n-core' ;
import {
2020-03-30 11:11:39 -07:00
IBinaryData ,
2019-10-31 09:24:56 -07:00
IDataObject ,
INodeExecutionData ,
2021-03-25 04:59:57 -07:00
INodePropertyOptions ,
2019-10-31 09:24:56 -07:00
INodeType ,
INodeTypeDescription ,
2021-04-16 09:33:36 -07:00
NodeOperationError ,
2019-10-31 09:24:56 -07:00
} from 'n8n-workflow' ;
2022-04-08 14:32:08 -07:00
import iconv from 'iconv-lite' ;
2021-03-25 04:58:54 -07:00
iconv . encodingExists ( 'utf8' ) ;
2021-03-25 04:59:57 -07:00
// Create options for bomAware and encoding
2021-03-25 04:58:54 -07:00
const bomAware : string [ ] = [ ] ;
const encodeDecodeOptions : INodePropertyOptions [ ] = [ ] ;
2021-03-25 05:19:11 -07:00
const encodings = ( iconv as any ) . encodings ; // tslint:disable-line:no-any
Object . keys ( encodings ) . forEach ( encoding = > {
if ( ! ( encoding . startsWith ( '_' ) || typeof encodings [ encoding ] === 'string' ) ) { // only encodings without direct alias or internals
if ( encodings [ encoding ] . bomAware ) {
2021-03-25 04:58:54 -07:00
bomAware . push ( encoding ) ;
}
2021-03-25 04:59:57 -07:00
encodeDecodeOptions . push ( { name : encoding , value : encoding } ) ;
2021-03-25 04:58:54 -07:00
}
} ) ;
2019-10-31 09:24:56 -07:00
2021-03-25 04:59:57 -07:00
encodeDecodeOptions . sort ( ( a , b ) = > {
if ( a . name < b . name ) { return - 1 ; }
if ( a . name > b . name ) { return 1 ; }
return 0 ;
} ) ;
2019-10-31 09:24:56 -07:00
export class MoveBinaryData implements INodeType {
description : INodeTypeDescription = {
displayName : 'Move Binary Data' ,
name : 'moveBinaryData' ,
icon : 'fa:exchange-alt' ,
group : [ 'transform' ] ,
version : 1 ,
subtitle : '={{$parameter["mode"]==="binaryToJson" ? "Binary to JSON" : "JSON to Binary"}}' ,
2021-07-03 05:40:16 -07:00
description : 'Move data between binary and JSON properties' ,
2019-10-31 09:24:56 -07:00
defaults : {
name : 'Move Binary Data' ,
color : '#7722CC' ,
} ,
inputs : [ 'main' ] ,
outputs : [ 'main' ] ,
properties : [
{
displayName : 'Mode' ,
name : 'mode' ,
type : 'options' ,
options : [
{
name : 'Binary to JSON' ,
value : 'binaryToJson' ,
description : 'Move data from Binary to JSON' ,
} ,
{
name : 'JSON to Binary' ,
value : 'jsonToBinary' ,
2022-05-06 14:01:25 -07:00
description : 'Move data from JSON to Binary' ,
2019-10-31 09:24:56 -07:00
} ,
] ,
default : 'binaryToJson' ,
2022-05-06 14:01:25 -07:00
description : 'From and to where data should be moved' ,
2019-10-31 09:24:56 -07:00
} ,
// ----------------------------------
// binaryToJson
// ----------------------------------
{
2022-06-03 10:23:49 -07:00
displayName : 'Set All Data' ,
2019-10-31 09:24:56 -07:00
name : 'setAllData' ,
type : 'boolean' ,
displayOptions : {
show : {
mode : [
'binaryToJson' ,
] ,
} ,
} ,
default : true ,
2021-10-27 13:00:13 -07:00
description : 'If all JSON data should be replaced with the data retrieved from binary key. Else the data will be written to a single key.' ,
2019-10-31 09:24:56 -07:00
} ,
{
displayName : 'Source Key' ,
name : 'sourceKey' ,
type : 'string' ,
displayOptions : {
show : {
mode : [
'binaryToJson' ,
] ,
} ,
} ,
default : 'data' ,
required : true ,
placeholder : 'data' ,
2022-04-22 09:29:51 -07:00
description : 'The name of the binary key to get data from. It is also possible to define deep keys by using dot-notation like for example: "level1.level2.currentKey".' ,
2019-10-31 09:24:56 -07:00
} ,
{
displayName : 'Destination Key' ,
name : 'destinationKey' ,
type : 'string' ,
displayOptions : {
show : {
mode : [
'binaryToJson' ,
] ,
setAllData : [
false ,
] ,
} ,
} ,
default : 'data' ,
required : true ,
placeholder : '' ,
2022-04-22 09:29:51 -07:00
description : 'The name the JSON key to copy data to. It is also possible to define deep keys by using dot-notation like for example: "level1.level2.newKey".' ,
2019-10-31 09:24:56 -07:00
} ,
// ----------------------------------
// jsonToBinary
// ----------------------------------
{
2022-06-03 10:23:49 -07:00
displayName : 'Convert All Data' ,
2019-10-31 09:24:56 -07:00
name : 'convertAllData' ,
type : 'boolean' ,
displayOptions : {
show : {
mode : [
'jsonToBinary' ,
] ,
} ,
} ,
default : true ,
2021-11-25 09:10:06 -08:00
description : 'If all JSON data should be converted to binary. Else only the data of one key will be converted.' ,
2019-10-31 09:24:56 -07:00
} ,
{
displayName : 'Source Key' ,
name : 'sourceKey' ,
type : 'string' ,
displayOptions : {
show : {
convertAllData : [
false ,
] ,
mode : [
'jsonToBinary' ,
] ,
} ,
} ,
default : 'data' ,
required : true ,
placeholder : 'data' ,
2022-04-22 09:29:51 -07:00
description : 'The name of the JSON key to get data from. It is also possible to define deep keys by using dot-notation like for example: "level1.level2.currentKey".' ,
2019-10-31 09:24:56 -07:00
} ,
{
displayName : 'Destination Key' ,
name : 'destinationKey' ,
type : 'string' ,
displayOptions : {
show : {
mode : [
'jsonToBinary' ,
] ,
} ,
} ,
default : 'data' ,
required : true ,
placeholder : 'data' ,
2022-04-22 09:29:51 -07:00
description : 'The name the binary key to copy data to. It is also possible to define deep keys by using dot-notation like for example: "level1.level2.newKey".' ,
2019-10-31 09:24:56 -07:00
} ,
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
placeholder : 'Add Option' ,
default : { } ,
options : [
2020-03-30 11:11:39 -07:00
{
displayName : 'Data Is Base64' ,
name : 'dataIsBase64' ,
type : 'boolean' ,
displayOptions : {
hide : {
'useRawData' : [
true ,
] ,
} ,
show : {
'/mode' : [
'jsonToBinary' ,
] ,
'/convertAllData' : [
false ,
] ,
} ,
} ,
default : false ,
2022-05-06 14:01:25 -07:00
description : 'Keeps the binary data as base64 string' ,
2020-03-30 11:11:39 -07:00
} ,
2019-10-31 09:24:56 -07:00
{
2019-11-01 03:12:55 -07:00
displayName : 'Encoding' ,
name : 'encoding' ,
2021-03-25 04:58:54 -07:00
type : 'options' ,
options : encodeDecodeOptions ,
2019-11-01 03:12:55 -07:00
displayOptions : {
show : {
'/mode' : [
'binaryToJson' ,
2021-03-25 04:58:54 -07:00
'jsonToBinary' ,
2019-11-01 03:12:55 -07:00
] ,
} ,
} ,
default : 'utf8' ,
description : 'Set the encoding of the data stream' ,
2019-10-31 09:24:56 -07:00
} ,
2021-03-25 04:58:54 -07:00
{
displayName : 'Strip BOM' ,
name : 'stripBOM' ,
displayOptions : {
show : {
'/mode' : [
'binaryToJson' ,
] ,
2021-03-25 05:19:11 -07:00
encoding : bomAware ,
2021-03-25 04:58:54 -07:00
} ,
} ,
type : 'boolean' ,
default : true ,
} ,
{
displayName : 'Add BOM' ,
name : 'addBOM' ,
displayOptions : {
show : {
'/mode' : [
'jsonToBinary' ,
] ,
2021-03-25 05:19:11 -07:00
encoding : bomAware ,
2021-03-25 04:58:54 -07:00
} ,
} ,
type : 'boolean' ,
default : false ,
} ,
2020-03-30 11:11:39 -07:00
{
displayName : 'File Name' ,
name : 'fileName' ,
type : 'string' ,
displayOptions : {
show : {
'/mode' : [
'jsonToBinary' ,
] ,
} ,
} ,
default : '' ,
placeholder : 'example.json' ,
2022-05-06 14:01:25 -07:00
description : 'The file name to set' ,
2020-03-30 11:11:39 -07:00
} ,
2019-11-01 03:07:17 -07:00
{
displayName : 'JSON Parse' ,
name : 'jsonParse' ,
type : 'boolean' ,
displayOptions : {
2020-03-30 11:11:39 -07:00
hide : {
'keepAsBase64' : [
true ,
] ,
} ,
2019-11-01 03:07:17 -07:00
show : {
'/mode' : [
'binaryToJson' ,
] ,
'/setAllData' : [
2020-10-22 06:46:03 -07:00
false ,
2019-11-01 03:07:17 -07:00
] ,
} ,
} ,
default : false ,
2022-05-06 14:01:25 -07:00
description : 'Run JSON parse on the data to get proper object data' ,
2019-11-01 03:07:17 -07:00
} ,
2019-10-31 09:24:56 -07:00
{
displayName : 'Keep Source' ,
name : 'keepSource' ,
type : 'boolean' ,
default : false ,
2021-08-26 10:44:09 -07:00
description : 'If the source key should be kept. By default it will be deleted.' ,
2019-10-31 09:24:56 -07:00
} ,
2020-03-30 11:11:39 -07:00
{
displayName : 'Keep As Base64' ,
name : 'keepAsBase64' ,
type : 'boolean' ,
displayOptions : {
hide : {
'jsonParse' : [
true ,
] ,
} ,
show : {
'/mode' : [
'binaryToJson' ,
] ,
'/setAllData' : [
false ,
] ,
} ,
} ,
default : false ,
2022-05-06 14:01:25 -07:00
description : 'Keeps the binary data as base64 string' ,
2020-03-30 11:11:39 -07:00
} ,
2019-10-31 09:24:56 -07:00
{
displayName : 'Mime Type' ,
name : 'mimeType' ,
type : 'string' ,
displayOptions : {
show : {
'/mode' : [
'jsonToBinary' ,
] ,
} ,
} ,
default : 'application/json' ,
placeholder : 'application/json' ,
description : 'The mime-type to set. By default will the mime-type for JSON be set.' ,
} ,
{
displayName : 'Use Raw Data' ,
name : 'useRawData' ,
type : 'boolean' ,
displayOptions : {
2020-03-30 11:11:39 -07:00
hide : {
'dataIsBase64' : [
true ,
] ,
} ,
2019-10-31 09:24:56 -07:00
show : {
'/mode' : [
'jsonToBinary' ,
] ,
} ,
} ,
default : false ,
2022-05-06 14:01:25 -07:00
description : 'Use data as is and do not JSON.stringify it' ,
2019-10-31 09:24:56 -07:00
} ,
] ,
2020-10-22 06:46:03 -07:00
} ,
2019-10-31 09:24:56 -07:00
] ,
} ;
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
const items = this . getInputData ( ) ;
const mode = this . getNodeParameter ( 'mode' , 0 ) as string ;
const returnData : INodeExecutionData [ ] = [ ] ;
let item : INodeExecutionData ;
let newItem : INodeExecutionData ;
let options : IDataObject ;
for ( let itemIndex = 0 ; itemIndex < items . length ; itemIndex ++ ) {
item = items [ itemIndex ] ;
2020-07-29 13:30:49 -07:00
options = this . getNodeParameter ( 'options' , itemIndex , { } ) as IDataObject ;
2019-10-31 09:24:56 -07:00
// Copy the whole JSON data as data on any level can be renamed
newItem = {
json : { } ,
2022-06-03 08:25:07 -07:00
pairedItem : {
item : itemIndex ,
} ,
2019-10-31 09:24:56 -07:00
} ;
if ( mode === 'binaryToJson' ) {
const setAllData = this . getNodeParameter ( 'setAllData' , itemIndex ) as boolean ;
const sourceKey = this . getNodeParameter ( 'sourceKey' , itemIndex ) as string ;
const value = get ( item . binary , sourceKey ) ;
if ( value === undefined ) {
// No data found so skip
continue ;
}
2021-03-25 04:58:54 -07:00
const encoding = ( options . encoding as string ) || 'utf8' ;
2022-01-27 23:46:30 -08:00
const buffer = await this . helpers . getBinaryDataBuffer ( itemIndex , sourceKey ) ;
let convertedValue : string ;
2019-10-31 09:24:56 -07:00
if ( setAllData === true ) {
// Set the full data
2022-01-27 23:46:30 -08:00
convertedValue = iconv . decode ( buffer , encoding , { stripBOM : options.stripBOM as boolean } ) ;
2019-11-01 03:07:17 -07:00
newItem . json = JSON . parse ( convertedValue ) ;
2019-10-31 09:24:56 -07:00
} else {
// Does get added to existing data so copy it first
newItem . json = JSON . parse ( JSON . stringify ( item . json ) ) ;
2020-03-30 11:11:39 -07:00
if ( options . keepAsBase64 !== true ) {
2022-01-27 23:46:30 -08:00
convertedValue = iconv . decode ( buffer , encoding , { stripBOM : options.stripBOM as boolean } ) ;
} else {
convertedValue = Buffer . from ( buffer ) . toString ( BINARY_ENCODING ) ;
2020-03-30 11:11:39 -07:00
}
2019-11-01 03:07:17 -07:00
if ( options . jsonParse ) {
convertedValue = JSON . parse ( convertedValue ) ;
}
2019-10-31 09:24:56 -07:00
const destinationKey = this . getNodeParameter ( 'destinationKey' , itemIndex , '' ) as string ;
set ( newItem . json , destinationKey , convertedValue ) ;
}
if ( options . keepSource === true ) {
// Binary data does not get touched so simply reference it
newItem . binary = item . binary ;
} else {
// Binary data will change so copy it
newItem . binary = JSON . parse ( JSON . stringify ( item . binary ) ) ;
unset ( newItem . binary , sourceKey ) ;
}
} else if ( mode === 'jsonToBinary' ) {
const convertAllData = this . getNodeParameter ( 'convertAllData' , itemIndex ) as boolean ;
const destinationKey = this . getNodeParameter ( 'destinationKey' , itemIndex ) as string ;
2021-03-25 04:58:54 -07:00
const encoding = ( options . encoding as string ) || 'utf8' ;
2019-10-31 09:24:56 -07:00
let value : IDataObject | string = item . json ;
if ( convertAllData === false ) {
const sourceKey = this . getNodeParameter ( 'sourceKey' , itemIndex ) as string ;
value = get ( item . json , sourceKey ) as IDataObject ;
}
if ( value === undefined ) {
// No data found so skip
continue ;
}
if ( item . binary !== undefined ) {
// Item already has binary data so copy it
newItem . binary = JSON . parse ( JSON . stringify ( item . binary ) ) ;
} else {
// Item does not have binary data yet so initialize empty
newItem . binary = { } ;
}
2020-03-30 11:11:39 -07:00
if ( options . dataIsBase64 !== true ) {
if ( options . useRawData !== true ) {
value = JSON . stringify ( value ) ;
}
2021-03-25 04:59:57 -07:00
value = iconv . encode ( value as string , encoding , { addBOM : options.addBOM as boolean } ) . toString ( BINARY_ENCODING ) ;
2019-10-31 09:24:56 -07:00
}
2020-03-30 11:11:39 -07:00
const convertedValue : IBinaryData = {
data : value as string ,
mimeType : ( options . mimeType as string ) || 'application/json' ,
2019-10-31 09:24:56 -07:00
} ;
2020-03-30 11:11:39 -07:00
if ( options . fileName ) {
convertedValue . fileName = options . fileName as string ;
}
2019-10-31 09:24:56 -07:00
set ( newItem . binary ! , destinationKey , convertedValue ) ;
if ( options . keepSource === true ) {
// JSON data does not get touched so simply reference it
newItem . json = item . json ;
} else {
// JSON data will change so copy it
if ( convertAllData === true ) {
// Data should not be kept and all data got converted. So simply set new as empty
newItem . json = { } ;
} else {
// Data should not be kept and only one key has to get removed. So copy all
// data and then remove the not needed one
newItem . json = JSON . parse ( JSON . stringify ( item . json ) ) ;
const sourceKey = this . getNodeParameter ( 'sourceKey' , itemIndex ) as string ;
unset ( newItem . json , sourceKey ) ;
}
}
} else {
2021-04-16 09:33:36 -07:00
throw new NodeOperationError ( this . getNode ( ) , ` The operation " ${ mode } " is not known! ` ) ;
2019-10-31 09:24:56 -07:00
}
returnData . push ( newItem ) ;
}
return [ returnData ] ;
}
2021-03-25 04:59:57 -07:00
}