Apex Dynamic Object Mapping for REST endpoints

Writing rest endpoints in Apex can be hard and exposing Salesforce fields to external application may not make allot of sense as custom fields end with __c and some fields are not writable or systems fields. I have worked on creating a Serializer that could serialize a Salesforce object mapping and translate that back to a Salesforce SObject.

Let say you can provide an endpoint to an app that could return the metadata of that object as well as the list of fields it expects. The mapping shows a map of keys and values. The keys are the Salesforce SObject fields and the values are the fields exposed to external application.

{
  "serializeNulls": true,
  "mapKeysFlag": true,
  "mapKeys": {
    "responseCode": "responseCode",
    "result": "result",
    "userId": "userId",
    "size": "size",
    "status": "status"
  },
  "userId": "00536000000erTFAAY",
  "result": {
    "external_id__c": "externalid",
    "sicdesc": "sicdesc",
    "accountsource": "accountsource",
    "site": "site",
    "rating": "rating",
    "description": "description",
    "tickersymbol": "tickersymbol",
    "ownership": "ownership",
    "numberofemployees": "numberofemployees",
    "billingcountry": "billingcountry",
    "billingpostalcode": "billingpostalcode",
    "billingstate": "billingstate",
    "billingcity": "billingcity",
    "billingstreet": "billingstreet",
    "parentid": "parentid",
    "type": "type",
    "name": "name",
    "id": "id"
  }
}

An application can send us data in the following way and we can serialize it to the Account sObject without doing any mapping.

{"externalid":"2333", "photourl": "http://blah.com", "website": "http://web.com", "accountnumber": "3243232", "fax": "2343432", "phone": "32222"}

Define a serializer to swap the key from application with the fields of the Salesforce object

public abstract class Rest_Serializer {

		public Rest_Serializer(){}

		private Map<String,String> mapKeys;
		private boolean serializeNulls = true;
		private boolean mapKeysFlag = true;

		public Rest_Serializer(Map<String,String> mapping){
			this.mapKeys = mapping;
		}

		public void setSerializeNulls(Boolean serializeNulls){
			this.serializeNulls = serializeNulls;
		}

		public void setMapKeysFlag(Boolean mapKeysFlag){
			this.mapKeysFlag = mapKeysFlag;
		}

		public String serialize(Object obj){
			String retString = JSON.serialize(obj);
			if (retString.length() < 50000){
				retString = transformStringForSerilization(retString);
				if(serializeNulls){
					retString = removeNullsFromJSON(retString);
				}
			} else {
		//throw exception
			}
			return retString;
		}

		public Object deserialize(String jsonString, System.Type type){
			jsonString = transformStringForDeserilization(jsonString);
			Object obj = JSON.deserialize(jsonString, type);
			return obj;
		}

		private String transformStringForSerilization(String s){
			if (mapKeys!=null && mapKeysFlag){
				return replaceAll(s, mapKeys);
			}
			return s;
		}

		private String transformStringForDeserilization(String s){
			Map<String,String> flippedMap = new Map<String,String>();
			if (mapKeys!=null && mapKeysFlag){
				for(String key : mapKeys.keySet()){
					flippedMap.put(mapKeys.get(key), key);
				}
			}
			return replaceAll(s, flippedMap);
		}

		private String removeNullsFromJSON(String s){
				return s.replaceAll('("[\\w]*":null,)|(,?"[\\w]*":null)','');
		}

		private String replaceAll(String s, Map<String,String> toFromMap){
			if (mapKeys!=null && mapKeysFlag){
				for(String key : toFromMap.keySet()){
						s = s.replaceAll('"'+key+'":', '"'+toFromMap.get(key)+'":');
				}
			}
			return s;
		}

		public Boolean getSerializeNulls(){
			return serializeNulls;
		}
}

Extend the Rest Serializer to create a DynamicModel that will take all fields and serialize them to an SObject

public class DynamicModel extends Rest_Serializer {
    public DynamicModel(Map<String,String> fieldMapping){
       super(fieldMapping);
    }
}

Get model name and return SObject Fieldsfields

	public static Map<String,String> getModelByObject(String sObjectName){
		Map<String,String> replaceFieldNames = new Map<String,String>();
		SObjectType accountType = Schema.getGlobalDescribe().get(sObjectName);
		Map<String,Schema.SObjectField> mfields = accountType.getDescribe().fields.getMap();
		for (String mfield : mfields.keySet()){
			//String uField = mfield.replace('[__c,_]','');
			replaceFieldNames.put(mfield, Rest_Util.toCamelCase(mfield));
		}
		return replaceFieldNames;
	}

	public static Map<String, Schema.DescribeFieldResult> getFieldMetaData(
	  Schema.DescribeSObjectResult dsor, Set<String> fields) {
		Map<String,Schema.DescribeFieldResult> finalMap = new Map<String, Schema.DescribeFieldResult>();

	  Map<String, Schema.SObjectField> objectFields = dsor.fields.getMap();

	  for(String field : fields){
	    if (objectFields.containsKey(field)) {
	      Schema.DescribeFieldResult dr = objectFields.get(field).getDescribe();
	      finalMap.put(field, dr);
	    }
	  }
	  return finalMap;
	}

Get SObject fields for an object and translate application fields to SObject fields and return object

    global with sharing class GetModel implements Rest_Dispatcher.Dispatchable {
			private Rest_Global_Request_Json requestBody;

			global String getURIMapping(){
					return Rest_Constants.URI_SETTINGS + '/model/{objectName}';
			}

			global void setRequestBody(Rest_Global_Request_Json requestBody){
					this.requestBody = requestBody;
			}

			global Rest_Global_Json execute(Map<String, String> parameters){
					Rest_Global_Json globalJson = new Rest_Global_Json();
					String sObjectName = parameters.get('objectName');
					Map<String,String> uiFieldNamesForObject = getModelByObject(sObjectName);
					BP_JSON.DynamicModel dynamicModel = new BP_JSON.DynamicModel(uiFieldNamesForObject);
					Schema.SObjectType convertType = Schema.getGlobalDescribe().get(sObjectName);
					Type classType = Type.forName(sObjectName);
					Object transformedObject = dynamicModel.deserialize(requestBody.getRequest(), classType);
					globalJson.setResult(transformedObject);
					return globalJson;
			}

			global override String toString(){
				Rest_Endpoint.Path path = new Rest_Endpoint.Path();
				path.setPath(getURIMapping());
				Rest_Endpoint endpoint = new Rest_Endpoint(path);
				return JSON.serialize(endpoint);
			}
	}
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: