The key problem here is that your types are wrong throughout your code. It's definitely confusing, but if I can be very direct: if you don't feel completely comfortable with your understanding of the Apex type system, you're not going to be successful writing this type of code.
Here's some key identities to keep in mind:
- A JSON list deserializes to a
List<Object>
. At run time, the contents of the List
(the Object
instances) will have a type appropriate to their value, which may be another complex type like a List
or Map
, or a primitive, and you can cast those elements individually.
- A JSON object deserializes to a
Map<String, Object>
. At run time, the contents of the Map
(the Object
instances) will have a type appropriate to their value, which may be another complex type like a List
or Map
, or a primitive, and you can cast those elements individually.
- The type
Map<String, Object>
is not related to the type sObject
. The two cannot be cast to one another.
- The type
Object
cannot be cast to the type sObject
unless the value is actually an sObject
instance, which it's not here, because untyped serialization never yields an sObject
value.
You can only cast a value to a type if it's actually an instance of that type, or of a type that descends from that type in the class hierarchy.* If you attempt a cast and you're wrong about the actual type of the value, you get an exception.
* I'm deliberately excluding discussion of weird features of the Apex type system; this is a general principle in strongly-typed object-oriented programming.
As I understood it would be easier to deserialize as maps. Since my payload consists of lists how do I get the field types of the list at runtime.
This is mistaken, or mis-framed. Your payload mixes lists and maps at various levels, and you don't have a choice about which you deserialize. You have to follow the structure of the payload: the outermost level is a Map, whose Accts
key contains a List, which contains Maps, and so on down the line.
List<sObject> fieldMapLevel_0 = (List<sObject>)fieldMap.get('Accts');
GetSobjectFields(newSobjLevel_0, fieldMapLevel_0);
This will fail. The value of the topmost key Accts
is not a List<sObject>
; it is a List<Map<String, Object>>
. You know that because it's a JSON list of JSON objects. As discussed above, because we know the type of all of the List
elements, we can cast to this specific type. We do not know the types of all of the Map
objects (and in fact they are not all the same), so we keep the generic Map<String, Object>
type.
You seem to have already written some of the code that you want, although there are a number of mistakes in logic that prevent it from working right now. If you want to iterate over the list of records in your JSON, just do so, treating every JSON list as a List<Object>
and every JSON object as a Map<String, Object>
. It would look something like this. This is an example: please do not copy and paste this code and expect it to work.
// I've omitted the first few lines here for clarity.
List<Object> recordList = (List<Object>)fieldMap.get('Accts');
List<sObject> objectsToInsert = new List<sObject>();
Type sobjectType = (sObject)Type.forName('Schema',mappings[0].sObjectApiNameLevel_0__c);
for (Object eachRecord : recordList) {
objectsToInsert.add(
GetSobjectFields(sobjectType, (Map<String, Object>)eachRecord);
}
}
insert objectsToInsert;
Then your GetSobjectFields()
method needs to change to take the appropriate types into account as well, and it definitely shouldn't be performing an insert
- you need to bulkify that DML, as above.
public static sObject GetSobjectFields(Type sobjectType, Map<String, Object> fieldMap ){
Note the different method signature. You accept a Type
, the sObject type you want to construct, and the map of field names to field values. Then,
newSObj = sobjectType.newInstance();
for (String fieldName : fieldMap.keySet()) {
// Do your logic here.
// Note that you've got a lot more code to write -
// you haven't implemented field-name mapping,
// or dealt with sub-lists.
// Then
newSObj.put(fieldName, value);
}
return newSObj;
}
I've skipped over a number of problems here, including the fact that fieldsMap
is never populated and that the logic populating the value
and key
variables doesn't make sense, but I think this rough skeleton is closer to where you'd need to be.
This is very similar to the code or structure that sfdcfox provided in this question. The version you've shown here has been changed from that post in a way that makes the logic invalid, because you've swapped around Maps and Lists and then put several parts of the code into a broken state. I'm not saying that to make you feel bad, but to emphasize how important understanding types is to writing this type of code successfully.
You've asked a lot of questions about this code and I completely understand that you're struggling. I want to offer an alternative. It looks like you are trying to write an API that can accept sObject data, mapped via a Custom Metadata Type. It looks like you're possibly also trying to accept multiple levels of data, like an sObject and its own child records, although you have not yet written the code to parse that data. This objective is certainly not impossible, but it's going to be a fair amount of tricky code to do in this fully-generic way.
Why not have your remote system directly call the Salesforce REST API, specifically, the Composite API or even better the sObject Tree API? These resources are designed to accept sObject data directly, including multiple records and nested sObjects of different types. They'll likely provide better performance, you won't have to write a line of Apex, and you don't need to worry about configuring Custom Metadata because the API already accepts your schema.
Alternately, you could use a middleware platform like Mulesoft or one of its many competitors to easily transform data from some incoming service into the format needed by the Salesforce API. What you're doing right now is probably the hardest way to achieve what you seem to be aiming at.