Thursday, May 16, 2013

APEX Map sorting

I am programming in APEX from less than a month and I've already written those lines several times... so I think this may be useful for someone else coming to this point for the first time.

We have a map, whose key may be Integer, String or whatever else is a Primitive type. We want to sort it, in ascending or descending order.

Here's a quick method:

public Map<Key_object, Value_object> sortMap (Map<Key_object, Value_object> theMap, String theOrder) {
    //create the return map
    Map<Key_object, Value_object> returnMap = new Map<Key_object, Value_object>();
    //get the set of the keys of the map we want to sort
    Set<Key_object> keySet = theMap.keySet();
    //create a list and add all the keys from the set
    //a small workaround needed because we can't sort a set
    List<Key_object> keyList = new List<Key_object>();
    keyList.addAll(keySet);
    //sort the list ascending (predefined behaviour)
    keyList.sort();
    if (theOrder == 'DESC')
        //iterate from the last to the first key over the ascending ordered key list
        for (Integer i = (keyList.size() - 1); i >= 0; i--)
            returnMap.put(keyList[i], theMap.get(keyList[i]));
    else
        //iterate from the first to the last key over the ascending ordered key list
        for (Integer i = 0; i < keyList.size(); i++)
            returnMap.put(keyList[i], theMap.get(keyList[i]));
    //return the sorted map
    return returnMap;
}

Of course we can omit the part regarding the order (if we want only ascending or descending order) or create two different methods for this.

APEX class getter for attributes by attribute name

A colleague came up the other day with this question:
Usually when you create an Apex class you access data using getters and setters with this direct attribute access syntax (dot-notation):
class MyClass {
    String name { get; set }
}
myC = new MyClass();
String theName = myC.name;  // <== dot-notation
I would now like to use this syntax for my class:
myC = new MyClass();
String theName = myC.get('name'); // <== generic attribute getter syntax
How do I achieve that for my Apex class? Observe that get(String attributeName) must be able to return different types since the attributes may be strings, integers, boolean etc.
Any ideas?

One solution is to use a Map for attributes. It's an easy solution (you don't need to write that much code) that requires small modification on setters of the attributes we want to "track", so that the map is populated with the current value at any time.
class MyClass {
    String name { get; set {
                             this.attributesMap.put('name', value);
                             name = value;
                       } }
    Map<String,Object> attributesMap { get; set; }
    public MyClass () {
        this.attributesMap = new Map<String,Object>();
    }
    public Object getter(String attrName) {
        return this.attributesMap.get(attrName);
    }
}

Object type must be used as the getter return type, since we can't overload methods on return type. Then, a forced casting is required for getting things working:

MyClass c = new MyClass();
c.name = 'Matt';
String a = (String) c.getter('name');

We can also create different getter methods for each type (getInt, getString, getDouble, etc.) to avoid writing casting on any assignment, but I think this is faster.

But, what if we don't want to modify the class at all? Here comes the dirty solution: we can use JSON functions to build an on-the-run getter on any instance attribute:

public class MyClass {
//(...)
    public Object getter(String attrName) {
        Map<String, Object> m = (Map<String, Object>) JSON.deserializeUntyped(JSON.serialize(this));
        return (Object) m.get(attrName);
    }
}

This will instantly create a Map of class instance attributes, returned as Object type, so that casting is still needed on assignment.
The JSON serialize-deserialize may cause some data type losing, for example Date and Datetime types will be translated as String, so that a little workaround is needed to get original data back
Date myDate = Date.valueOf((String) c.get('myDateAttribute');


Hello World

...how can a programming-related blog first post be different?

I'm a Salesforce Developer - still not certified. I'd like to share here whatever I think that could be useful for other people out there. So, let's get it started and... happy coding! :-)