Okay so I gave up on trying to dynamically use
as Object {class: objectProperties.ObjectName}
inside of the DataWeave script
Instead I decided that since we can serialize and deserialize SObjects I would have the Apex code handle the object aspect.
First I tried simply
List<SObject> objectList = (List<SObject>)JSON.deserialize(jsonText,List<SObject>.class);
But the issue with this is I got an error about polymorphic objects. This is because the output from the DataWeave looked like:
[
{
"FirstName": "Jane",
"LastName": " Austin",
"Title": " CEO",
"Height__c": "7",
"Priority__c": null,
"Hobbies__c": null
},
{
"FirstName": "Bob",
"LastName": " Smith",
"Title": " COO",
"Height__c": " 6",
"Priority__c": null,
"Hobbies__c": null
}
]
Salesforce needs an additional attributes within the JSON to determine which type of SObject. Salesforce wants the JSON to look like:
[
{
"attributes": {
"type": "Contact"
},
"FirstName": "Jane",
"LastName": " Austin",
"Title": " CEO",
"Height__c": "7",
"Priority__c": null,
"Hobbies__c": null
},
{
"attributes": {
"type": "Contact"
},
"FirstName": "Bob",
"LastName": " Smith",
"Title": " COO",
"Height__c": " 6",
"Priority__c": null,
"Hobbies__c": null
}
]
Notice the object name comes after the "type"
So here is what I did. I modified my DataWeave Script to simply output JSON (forget the output application/apex)
%dw 2.0
input csvData application/csv
input fieldMappings application/json
input objectProperties application/json
var applyMapping = (in, mappings) -> (
mappings map (fieldMapping) -> {
(fieldMapping.target) : if(in[fieldMapping.source] != "") in[fieldMapping.source] else fieldMapping."defaultValue"
}
)
var reduceThis = (in) -> (
in
reduce ($$ ++ $)
)
var attributeThis = (in) -> (
{
attributes: {
"type" : objectProperties.ObjectName
}
} ++ in
)
output application/json
---
csvData map ((row) ->
(
attributeThis(reduceThis(applyMapping(row,fieldMappings)))
)
)
This will read each row of the CSV. It will dynamically reach from fieldMappings to figure out how to map the CSV columns into JSON attributes of my choosing (i.e. Height__c) For some reason without the reduce() it was causing each column to be it's own JSON object, but reduce gave me a single object per row of the CSV And then using the 'attributeThis' function I was able to concatenate onto each object the "attribute" along with its "type" and the type is pulling from the objectProperties input.
This allows for a 100% generic DataWeave script that can take ANY CSV file and convert it into ANY Salesforce Object based on 3 input files: CSV file Field Mapping file (json formatted) Object Properties file (json formatted)
I didn't know how to feed DataWeave a single string thus the overly complex Object Properties file
But remember this doesn't give you a list of SObjects directly, inside of your Apex Code you have to deserialize the output from DataWeave.
Below is the Apex code I have that is invokable in Flow.
This allows me in Flow to prompt for a CSV file and then insert or upsert the data into Salesforce
Here is my Apex code (Please note: this is a PROOF OF CONCEPT, the code is rough, uncommented, and has a ways to go before it's ready for prime time)
/**
* Created by Caleb Sidel on 12/12/24.
*/
public class CSVData
{
public class FlowInput
{
@InvocableVariable(Label='Content Document Ids' required=true)
public List<String> contentDocumentIds;
@InvocableVariable(Label='Field Mappings' required=true)
public String jsonFieldMappings;
@InvocableVariable(Label='Object Name' required=true)
public String objectName;
}
@InvocableMethod(label='Read Requirements CSV File')
public static List<List<SObject>> readRequirementsCSVFile(List<FlowInput> inputs)
{
List<List<SObject>> resultList = new List<List<SObject>>();
ContentVersion doc = [SELECT Id, VersionData FROM ContentVersion WHERE ContentDocumentId = :inputs[0].contentDocumentIds[0] AND IsLatest = TRUE];
System.debug('inputs[0].objectName = ' + inputs[0].objectName);
Map<String, String> objectPropertiesMap = new Map<String, String>();
objectPropertiesMap.put('ObjectName',inputs[0].objectName);
String jsonObjectProperties = JSON.serialize(objectPropertiesMap);
System.debug('jsonObjectProperties = ' + jsonObjectProperties);
Blob csvFileBody = doc.VersionData;
String csvAsString = csvFileBody.toString();
DataWeave.Script dwscript = new DataWeaveScriptResource.RequirementsFromCSV();
DataWeave.Result dwresult = dwscript.execute(new Map<String, Object>{
'csvData' => csvAsString,
'fieldMappings' => inputs[0].jsonFieldMappings,
'objectProperties' => jsonObjectProperties
});
System.debug('dwresult = ' + dwresult);
System.debug('dwresult.getValue() = ' + dwresult.getValue());
System.debug('dwresult.getValueAsString() = ' + dwresult.getValueAsString());
//So our DataWeave results in a JSON string that represents a list of SObjects
String jsonText = dwresult.getValueAsString();
System.debug(jsonText);
List<SObject> sObjectList = (List<SObject>)JSON.deserialize(jsonText,List<Sobject>.class);
resultList.add(sObjectList);
return resultList;
}
}
In summary - there may be a more elegant DataWeave script, and there may be a way to dynamically use
as Object
directly, but for the time being I'm pretty satisfied that I got something (anything) to work.
If you are a DataWeave guru and you have any ideas for the dynamic as Object, that would be awesome though! Thank you and have a great day!