Only some types work as Apex map keys from Visualforce

The “Referencing Apex Maps and Lists” section of the Visualforce Developer’s Guide provides examples of how to reference data held in an Apex map in the controller from a Visualforce page. I haven’t found examples where the key values used have been other than strings or integers and the documentation is not explicit. But now that there is support for Non-primitive Types in Map Keys and Sets, I thought it would be worth trying a DateTime value as a map key in a page I have that heavily uses DateTime objects.

But DateTime as a map key type does not work from Visualforce:

Incorrect parameter type for subscript. Expected java.lang.Class, received DateTime

To get some sense of what does and what does not work I created the controller and page listed at the end of this post (both set to API 26.0). I found two further failing map key types.

Time as a map key type results in:

Unsupported type shared.xml.soap.Time encountered.

A custom Apex class as a map key type results in:

Map key Custom:[value=987] not found in map

i.e. value semantics are not supported (but the generally less useful reference semantics are).

I’ll create a case with Salesforce about the types that don’t work.

<apex:page controller="MapTestController">
  <h1>Map Test</h1>
  <br/><apex:outputText value="{!stringKeyMap[stringKey]}"/>
  <br/><apex:outputText value="{!integerKeyMap[integerKey]}"/>
  <br/><apex:outputText value="{!longKeyMap[longKey]}"/>
  <br/><apex:outputText value="{!decimalKeyMap[decimalKey]}"/>
  <br/><apex:outputText value="{!dateKeyMap[dateKey]}"/>
  <br/><apex:outputText value="{!customByReferenceKeyMap[customByReferenceKey]}"/>
  <!--
  <br/><apex:outputText value="{!timeKeyMap[timeKey]}"/>
  -->
  <!--
  <br/><apex:outputText value="{!dateTimeKeyMap[dateTimeKey]}"/>
  -->
  <!--
  <br/><apex:outputText value="{!customByValueKeyMap[customByValueKey]}"/>
  -->
</apex:page>
public class MapTestController {

    public String stringKey {
        get {
            return 'key';
        }
    }
    public Map<String, String> stringKeyMap {
        get {
            return new Map<String, String>{stringKey => 'string value'};
        }
    }
    
    public Integer integerKey {
        get {
            return 1234;
        }
    }
    public Map<Integer, String> integerKeyMap {
        get {
            return new Map<Integer, String>{integerKey => 'integer value'};
        }
    }
    
    public Long longKey {
        get {
            return 1234;
        }
    }
    public Map<Long, String> longKeyMap {
        get {
            return new Map<Long, String>{longKey => 'long value'};
        }
    }
    
    public Decimal decimalKey {
        get {
            return 3.25;
        }
    }
    public Map<Decimal, String> decimalKeyMap {
        get {
            return new Map<Decimal, String>{decimalKey => 'decimal value'};
        }
    }
    
    public Date dateKey {
        get {
            return Date.newInstance(2012, 1, 1);
        }
    }
    public Map<Date, String> dateKeyMap {
        get {
            return new Map<Date, String>{dateKey => 'date value'};
        }
    }
    
    public Time timeKey {
        get {
            return Time.newInstance(8, 20, 30, 0);
        }
    }
    public Map<Time, String> timeKeyMap {
        get {
            return new Map<Time, String>{timeKey => 'time value'};
        }
    }
    
    public DateTime dateTimeKey {
        get {
            return DateTime.newInstance(2012, 1, 1, 0, 0, 0);
        }
    }
    public Map<DateTime, String> dateTimeKeyMap {
        get {
            return new Map<DateTime, String>{dateTimeKey => 'date time value'};
        }
    }
    
    public class Custom {
        private Integer value;
        public Custom(Integer value) {
            this.value = value;
        }
        public Boolean equals(Object other) {
            return this.value == ((Custom) other).value;
        }
        public Integer hashCode() {
            return value;
        }
    }
    
    private static final Custom customInstance = new Custom(321);
    public Custom customByReferenceKey {
        get {
            return customInstance;
        }
    }
    public Map<Custom, String> customByReferenceKeyMap {
        get {
            return new Map<Custom, String>{customByReferenceKey => 'custom (by reference) value'};
        }
    }
    
    public Custom customByValueKey {
        get {
            return new Custom(987);
        }
    }
    public Map<Custom, String> customByValueKeyMap {
        get {
            return new Map<Custom, String>{customByValueKey => 'custom (by value) value'};
        }
    }
}
Advertisements

5 thoughts on “Only some types work as Apex map keys from Visualforce

  1. Well, I figured out (at least feasibly) why Datetime… and perhaps other time structures don’t work as map keys.

    After a very painful day of debugging a test class I am writing, I checked the .getTime() for the key I used to to put data into the map, against the .getTime() of the keys in the actual map. They are different…

    Var.getTime() used with .put() ==========> 1396047028386
    Key found by iterating through .keySet() ==> 1396047028000

    Why on earth this rounding would happen is beyond me.

    So it would appear that as long as you use the or some other looping structure on your map’s key-set, and not direct property access the rounding will line up, and the Map access will go off without a hitch.

    • Well after some time trying to replicate the problem in a simpler setting, I have been unable to do so…

      I cannot seem to duplicate the issue outside of my Test class. I am doing a lot of complicated looping, assignment, map manipulation, etc. in the class I’m testing, so it is difficult to ascertain the point where the Datetime gets unexpectedly rounded.

      But I wanted to clarify that this rounding error appears to be the exception, not the rule.

    • Interesting. Perhaps immediately putting any Datetime touched in Apex code through a method that truncates down to the nearest second would work-around the problem and allow the Datetime to be used as a key?

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