On the assumption that you want to keep the JSON received like you show it, you can do this by having additional data (from your metadata) that covers:
- Mapping the array property name to the SObject type, e.g. "Accts" is mapped to "Contact"
- Mapping each array property's nested object's properties to fields of that SObject type, e.g. "name" is mapped to "Name" (or "FirstName") and "title" is mapped to "Title"
You might, thus, have a JSON configuration that looks like:
{
"mappings": [
{
"baseProperty": "Accts",
"objectType": "Contact",
"properties": [
{
"fromProperty": "name",
"toField": "Name"
},
{
"fromProperty": "title",
"toField": "Title"
}
]
}
]
}
Note that, because "mappings" is an array, you can actually have multiple "base properties" supported. You might have one called "Accts" for Contacts, one called "Opps" for Opportunities, another called "Prods" for Products etc. All of the mappings can be defined in one CMT record, for example, though of course this has the downside that you always load all the mappings even when a given received JSON data set only uses one of the mappings. More on that later.
You can read this data into POTATOs (plain old transactional Apex transient objects - instances of an Apex class) using JSON's deserialize
method, using POTATOs like:
class Config {
public List<ObjectMapping> mappings;
}
class ObjectMapping {
public String baseProperty;
public String objectType;
public List<PropertyMapping> properties;
}
class PropertyMapping {
public String fromProperty;
public String toField;
}
To minimize the overhead in getting the SObject schema data avoid the inefficient global describes. Thus, when you are processing the meta ("config") information you can do something like the following (assuming you have read the "config" into the above Apex object structure using JSON.deserialize
as shown at the start of this example code) since this is more efficient:
Config config = JSON.deserialize(theJSONString, Config.class);
Map<String, DescribeSObjectResult> describesByName = new Map<String, DescribeSObjectResult>();
Map<String, ObjectMapping> mappingsByBaseProperty = new Map<String, ObjectMapping>();
for (ObjectMapping mapping : config.mappings) {
SObject obj = (SObject) Type.forName(mapping.objectType).newInstance();
DescribeSObjectResult describe = obj.getSObjectType().getDescribe();
describesByName.put(mapping.objectType, describe);
mappingsByBaseProperty.put(mapping.baseProperty, mapping);
}
Now you're ready to process the received JSON data by simply iterating the properties, finding the equivalent object mapping from the mappingsByBaseProperty
map and using the SObject describe available in describesByName
, obtained using the selected object mapping's objectType
.
All the toField
DescribeFieldResult values are easily obtained through the DescribeSObjectResult's fields.getMap()
as needed (or by simply using SObject's put(fieldName, value)
to populate the required field if the JSON data is of the required type).
If you want to split your mappings up, to ensure only mappings relevant to a given received JSON data set are processed, you could always take the approach that the CMT record's "name" is actually the name of the base property itself (e.g. "Accts") and then have two fields in the CMT; one to store the object type name and the other to store a JSON string defining the property mappings. This means you can load the configuration selectively if you wish by first analysing the "base properties" (the top level keys in your Map<String, Object>
generated representation from parsing the received JSON using JSON.deserializeUntyped
).
You could load your CMT records using something like:
Map<String, Object> data = JSON.deserializeUntyped(theReceivedJSONString);
List<Json_Schema__mdt> configs = [
SELECT ObjectType__c, PropertyMappings__c
FROM Json_Schema__mdt
WHERE DeveloperName IN :data.keySet()
];
This approach clearly changes the other items I outlined above, but I would hope you can understand what I'm getting at without further detailed description.
I would recommend creating a JSON schema to describe the configuration structure (be that the whole object mappings or the property mappings) to aid editing of the configuration itself in a schema aware editor.