0

I want to create a generic webservice. As I understood going the way of using Schema.getGlobalDescribe() will result in performance issues. I decided to try using JSON Schema trough Custom metadata adding the JSON Schema, Salesforce object API name and JSON objectname.

I want to get the name of the JSON object from the request dynamically so that I can get the correct metadata record for the schema. How can I achieve this Dynamically given below example

JSON

  {
  "Accts": [
    {
      "name": "Alice",
      "title": "title1"
    },
    {
      "name": "Ben",
      "title": "title2"
    }
  ]
}

Apex Update

Example: 1.Get object from JSON dynamically/generic = 'Accts'

Json_Schema__mdt JsonMdt = [select sObject__c, Json_Object__c, Json_Schema__c from Json_Schema__mdt where Json_Object__c = 'Accts'];
       
String ObjectName = JsonMdt.get('sObject__c').toString();
SObjectType sobjType = ((SObject)(Type.forName('Schema.' + ObjectName).newInstance())).getSObjectType();  

How do I get Accts dynamically

Please give me some suggestions to do this or point me in the right direction. Thanks in advance.

1

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.

2
  • Thank you for you detailed explanation. Please bear with me since im trying to grasp the concept of JSON scheme. I want to have 1 url where several request can be done. Based on the request I want to get the correct Schema from metadata. Is there a way to realize this? I updated my question with an example. Thanks in advance.
    – Thomas
    Jan 19 at 9:36
  • @Thomas I have updated my answer. Take a look. I added info about how this could be a single configuration or separate configurations per "base property" (such as "Accts") in the REST API.
    – Phil W
    Jan 19 at 11:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.