function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Kenji775Kenji775 

Automatic type casting when using sObject.put()

Hey all,

 

This is one of those questions when I feel like I'm probably going about a simple process the wrong way and over complicating it, but I'm not sure the alternative. So I'll ask my question and if you have a better approach, please feel free to share.

 

The deal is that I have an Apex REST service that allows a user to pass in an object type, and any field values in the URL.

EX : http://na2.salesforce.com/apex/reservice/contact?firstname=frank&lastname=jones&age__c=21

 

My code works fine for any string values, but chokes on numerics. Because I instantiate a new instance of the type of object they pass in, then I loop over all the fields in the query string and dynamically add them to the sObject. Of course as far as apex is concerned all those arguments in the query string are in fact strings, so when adding non string things to the sObject it explodes. 

 

Ideally I could use the get describe info about the field I'm inserting to find it's type and cast the value to be inserted to the correct type, but as far as I know there isn't a way to do dynamic casting at runtime. A series of if statments is about the only alternative I see, but that feels really dirty. 

 

This is what I have currently (you can see I'm kind of using the if statment path, trying to make it as simple as possible).

 

Just to be clear, this code works, it's just not as efficient/dynamic as I'd like.

 

    public static sObject saveSObject(string objectType, string recordid, RestRequest req)
    {
        //create a generic sObject to contain the update values.
        sObject updateObj;      
                
        //get the describe object for the type of object passed in (its just passed in as a string from the URL)
        Schema.sObjectType objectDef = Schema.getGlobalDescribe().get(objectType).getDescribe().getSObjectType();
        
        //find all the fields for this object type
        Map<String, Schema.SobjectField> ObjectFieldsMap = objectDef.getDescribe().fields.getMap();
        
 
        //this method can handle updates or inserts. If a record ID was passed in, 
        //cast the object as the type represented by the ID. If not, just create a
        //new object of the type found in the object describe.
        if(recordId != null)
        {
            updateObj = objectDef.newSobject(recordid);
        }
        else
        {
            updateObj = objectDef.newSobject();
        }    
        // populate the object's fields by looping over all the params in the rest request.
        for (String key : req.params.keySet())
        {
            // only add params if they are valid field on the object
            if (ObjectFieldsMap.containsKey(key))
            {
                //figure out the type of this field so we can cast it to the correct type
                string fieldType = ObjectFieldsMap.get(key).getDescribe().getType().name().ToLowerCase();
                
                //since I don't know how to do, or if it's even possible to do dynamic casting we need a 
                //series of if statments to handle the casting to numeric types. I think all the others should
                //be fine if left as a string. Dates might explode, not sure.
                
                
                if(fieldType == 'currency' || fieldType == 'double' || fieldType == 'percent' || fieldType == 'decimal' )
                {
                    updateObj.put(key, decimal.valueOf(req.params.get(key).trim())); 
                }
                else if(fieldType == 'boolean')
                {
                    updateObj.put(key, Boolean.valueOf(req.params.get(key))); 
                }                   
                else if(fieldType == 'date')
                {
                    updateObj.put(key, date.valueOf(req.params.get(key))); 
                }                
                else
                {
                    updateObj.put(key, req.params.get(key));
                }
            }
            else
            {
                system.debug('Invalid field: '+ key + ' for object type ' + objectType);
            }
        }
        //update/insert the object
        upsert updateObj;
        
        //return the saved object.
        return updateObj;
        
    }

 

blacknredblacknred

I haven't tested it, but something like these should work:

 

(...)

if (ObjectFieldsMap.containsKey(key)){
    JSONParser parser = JSON.createParser('{"":"'+req.params.get(key)+'"}');
    parser.nextToken();
    parser.nextValue();
                
    updateObj.put(key, parser.readValueAs(ObjectFieldsMap.get(key).getDescribe().getType()));
}
	else{
		system.debug('Invalid field: '+ key + ' for object type ' + objectType);
}

(...)

 

Let me know if it does and there is probably some place for improvement by creating the parser previously to the loop.

Kenji775Kenji775

Thanks! 

I do appreciate the reply, but unfortunatly I don't think it's quite going to work. I'm not using any JSON here (just reading params from the URL). The magic method your code uses, the readValueAs I think is a JSON only feature. If there was something like that for a string data type that would be awesome, but I don't think such a thing exists.

blacknredblacknred

In this line

JSONParser parser = JSON.createParser('{"":"'+req.params.get(key)+'"}');

 the parser JSON gets constructed from your parameter string, so you 'build' the JSON data.

 

this is a working example: 

Opportunity o = new Opportunity();

JSONParser parser = JSON.createParser('{"":"5"}');

parser.nextToken();
parser.nextValue();     

o.put('Amount',parser.readValueAs(Integer.class));

System.assertEquals(o.Amount, 5);

 

RJ PalomboRJ Palombo

Kenji755, did you ever get this figured out? I'm running into the same issue.

 

Also, to the other poster: The JSON example would not help us as we need a dynamic solution and would not be able to cast something to a concrete type. As Kenji stated, it looks as if the only way is to use if/else or try/catch to figure out the type and cast from there.

ZoomzoomZoomzoom

I'm hitting the same issue. The best way I can think to deal with it is to:

  • Create a TypeConversion abstract class with a convert() virtual method
  • Create a derived class for each conversions (String, numbers, dates, etc.)
  • Go over the sObject fields that need to be read and determine their types
  • Store in a List or Map the proper TypeConversion instance
  • For each field of each record, get the field's TypeConversion instance and call its convert() method

Kind of a hassle, but at least it's an OO way ;-)

Timo BierbrauerTimo Bierbrauer
Old, but really usuful from Backnred but a little change is needed.

getDescribe().getType() will give you a DisplayType, but for readvalueas you need a System.Type. So you need to System.Type.forName(DisplayType) before.

String type = descMap.get(*fieldName*).getDescribe().getType().name();
*Object*.put(*fieldName*, parser.readValueAs(System.Type.forName(type)));