2023-02-23 07:16:05 -08:00
import get from 'lodash.get' ;
import set from 'lodash.set' ;
import unset from 'lodash.unset' ;
2022-12-11 05:10:54 -08:00
import prettyBytes from 'pretty-bytes' ;
2019-10-31 09:24:56 -07:00
2023-01-27 03:22:44 -08:00
import type { IExecuteFunctions } from 'n8n-core' ;
import { BINARY_ENCODING } from 'n8n-core' ;
2021-03-25 04:58:54 -07:00
2023-01-27 03:22:44 -08:00
import type {
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 ,
} from 'n8n-workflow' ;
2023-01-27 03:22:44 -08:00
import { deepCopy , jsonParse , NodeOperationError , fileTypeFromMimeType } from 'n8n-workflow' ;
2019-10-31 09:24:56 -07:00
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 [ ] = [ ] ;
2022-12-02 06:25:21 -08:00
const encodings = ( iconv as any ) . encodings ;
2022-08-17 08:50:24 -07:00
Object . keys ( encodings ) . forEach ( ( encoding ) = > {
if ( ! ( encoding . startsWith ( '_' ) || typeof encodings [ encoding ] === 'string' ) ) {
// only encodings without direct alias or internals
2021-03-25 05:19:11 -07:00
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 ) = > {
2022-08-17 08:50:24 -07:00
if ( a . name < b . name ) {
return - 1 ;
}
if ( a . name > b . name ) {
return 1 ;
}
2021-03-25 04:59:57 -07:00
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 : {
2022-08-17 08:50:24 -07:00
mode : [ 'binaryToJson' ] ,
2019-10-31 09:24:56 -07:00
} ,
} ,
default : true ,
2022-08-17 08:50:24 -07:00
description :
'Whether 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 : {
2022-08-17 08:50:24 -07:00
mode : [ 'binaryToJson' ] ,
2019-10-31 09:24:56 -07:00
} ,
} ,
default : 'data' ,
required : true ,
placeholder : 'data' ,
2022-08-17 08:50:24 -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 : {
2022-08-17 08:50:24 -07:00
mode : [ 'binaryToJson' ] ,
setAllData : [ false ] ,
2019-10-31 09:24:56 -07:00
} ,
} ,
default : 'data' ,
required : true ,
placeholder : '' ,
2022-08-17 08:50:24 -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 : {
2022-08-17 08:50:24 -07:00
mode : [ 'jsonToBinary' ] ,
2019-10-31 09:24:56 -07:00
} ,
} ,
default : true ,
2022-08-17 08:50:24 -07:00
description :
'Whether 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 : {
2022-08-17 08:50:24 -07:00
convertAllData : [ false ] ,
mode : [ 'jsonToBinary' ] ,
2019-10-31 09:24:56 -07:00
} ,
} ,
default : 'data' ,
required : true ,
placeholder : 'data' ,
2022-08-17 08:50:24 -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 : {
2022-08-17 08:50:24 -07:00
mode : [ 'jsonToBinary' ] ,
2019-10-31 09:24:56 -07:00
} ,
} ,
default : 'data' ,
required : true ,
placeholder : 'data' ,
2022-08-17 08:50:24 -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 : {
2022-08-17 08:50:24 -07:00
useRawData : [ true ] ,
2020-03-30 11:11:39 -07:00
} ,
show : {
2022-08-17 08:50:24 -07:00
'/mode' : [ 'jsonToBinary' ] ,
'/convertAllData' : [ false ] ,
2020-03-30 11:11:39 -07:00
} ,
} ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to keep 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 : {
2022-08-17 08:50:24 -07:00
'/mode' : [ 'binaryToJson' , '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 : {
2022-08-17 08:50:24 -07:00
'/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 : {
2022-08-17 08:50:24 -07:00
'/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 : {
2022-08-17 08:50:24 -07:00
'/mode' : [ 'jsonToBinary' ] ,
2020-03-30 11:11:39 -07:00
} ,
} ,
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 : {
2022-08-17 08:50:24 -07:00
keepAsBase64 : [ true ] ,
2020-03-30 11:11:39 -07:00
} ,
2019-11-01 03:07:17 -07:00
show : {
2022-08-17 08:50:24 -07:00
'/mode' : [ 'binaryToJson' ] ,
'/setAllData' : [ false ] ,
2019-11-01 03:07:17 -07:00
} ,
} ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to 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 ,
2022-06-20 07:54:01 -07:00
description : 'Whether 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 : {
2022-08-17 08:50:24 -07:00
jsonParse : [ true ] ,
2020-03-30 11:11:39 -07:00
} ,
show : {
2022-08-17 08:50:24 -07:00
'/mode' : [ 'binaryToJson' ] ,
'/setAllData' : [ false ] ,
2020-03-30 11:11:39 -07:00
} ,
} ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to keep 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 : {
2022-08-17 08:50:24 -07:00
'/mode' : [ 'jsonToBinary' ] ,
2019-10-31 09:24:56 -07:00
} ,
} ,
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 : {
2022-08-17 08:50:24 -07:00
dataIsBase64 : [ true ] ,
2020-03-30 11:11:39 -07:00
} ,
2019-10-31 09:24:56 -07:00
show : {
2022-08-17 08:50:24 -07:00
'/mode' : [ 'jsonToBinary' ] ,
2019-10-31 09:24:56 -07:00
} ,
} ,
default : false ,
2022-06-20 07:54:01 -07:00
description : 'Whether to 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 ] ;
2022-12-02 12:54:28 -08:00
options = this . getNodeParameter ( 'options' , itemIndex , { } ) ;
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
2022-12-02 12:54:28 -08:00
if ( setAllData ) {
2019-10-31 09:24:56 -07:00
// Set the full data
2022-08-17 08:50:24 -07:00
convertedValue = iconv . decode ( buffer , encoding , {
stripBOM : options.stripBOM as boolean ,
} ) ;
2022-10-21 11:52:43 -07:00
newItem . json = jsonParse ( convertedValue ) ;
2019-10-31 09:24:56 -07:00
} else {
// Does get added to existing data so copy it first
2022-10-21 08:24:58 -07:00
newItem . json = deepCopy ( item . json ) ;
2019-10-31 09:24:56 -07:00
2020-03-30 11:11:39 -07:00
if ( options . keepAsBase64 !== true ) {
2022-08-17 08:50:24 -07:00
convertedValue = iconv . decode ( buffer , encoding , {
stripBOM : options.stripBOM as boolean ,
} ) ;
2022-01-27 23:46:30 -08:00
} 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 ) {
2022-10-21 11:52:43 -07:00
convertedValue = jsonParse ( convertedValue ) ;
2019-11-01 03:07:17 -07:00
}
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
2022-10-21 08:24:58 -07:00
newItem . binary = deepCopy ( item . binary ) ;
2019-10-31 09:24:56 -07:00
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 ;
2022-12-02 12:54:28 -08:00
if ( ! convertAllData ) {
2019-10-31 09:24:56 -07:00
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
2022-10-21 08:24:58 -07:00
newItem . binary = deepCopy ( item . binary ) ;
2019-10-31 09:24:56 -07:00
} else {
// Item does not have binary data yet so initialize empty
newItem . binary = { } ;
}
2022-12-11 05:10:54 -08:00
const mimeType = ( options . mimeType as string ) || 'application/json' ;
const convertedValue : IBinaryData = {
data : '' ,
mimeType ,
fileType : fileTypeFromMimeType ( mimeType ) ,
} ;
2020-03-30 11:11:39 -07:00
if ( options . dataIsBase64 !== true ) {
2022-12-11 05:10:54 -08:00
if ( options . useRawData !== true || typeof value === 'object' ) {
2020-03-30 11:11:39 -07:00
value = JSON . stringify ( value ) ;
}
2022-12-11 05:10:54 -08:00
convertedValue . fileSize = prettyBytes ( value . length ) ;
convertedValue . data = iconv
. encode ( value , encoding , { addBOM : options.addBOM as boolean } )
2022-08-17 08:50:24 -07:00
. toString ( BINARY_ENCODING ) ;
2022-12-11 05:10:54 -08:00
} else {
convertedValue . data = value as unknown as string ;
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 ;
}
2022-12-02 12:54:28 -08:00
set ( newItem . binary , destinationKey , convertedValue ) ;
2019-10-31 09:24:56 -07:00
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
2022-12-02 12:54:28 -08:00
if ( convertAllData ) {
2019-10-31 09:24:56 -07:00
// 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
2022-10-21 08:24:58 -07:00
newItem . json = deepCopy ( item . json ) ;
2019-10-31 09:24:56 -07:00
const sourceKey = this . getNodeParameter ( 'sourceKey' , itemIndex ) as string ;
unset ( newItem . json , sourceKey ) ;
}
}
} else {
2022-08-17 08:50:24 -07:00
throw new NodeOperationError ( this . getNode ( ) , ` The operation " ${ mode } " is not known! ` , {
itemIndex ,
} ) ;
2019-10-31 09:24:56 -07:00
}
returnData . push ( newItem ) ;
}
return [ returnData ] ;
}
2021-03-25 04:59:57 -07:00
}