Serializing Map<Date, String> with Jackson

The default map key serializer is StdKeySerializer, and it simply does this.

String keyStr = (value.getClass() == String.class) ? ((String) value) : value.toString();
jgen.writeFieldName(keyStr);

You could make use of the SimpleModule feature, and specify a custom key serializer, using the addKeySerializer method.


And here's how that could be done.

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.type.MapType;
import org.codehaus.jackson.map.type.TypeFactory;

public class CustomKeySerializerDemo
{
  public static void main(String[] args) throws Exception
  {
    Map<Date, String> myMap = new HashMap<Date, String>();
    myMap.put(new Date(), "now");
    Thread.sleep(100);
    myMap.put(new Date(), "later");

    ObjectMapper mapper = new ObjectMapper();
    System.out.println(mapper.writeValueAsString(myMap));
    // {"Mon Jul 04 11:38:36 MST 2011":"now","Mon Jul 04 11:38:36 MST 2011":"later"}

    SimpleModule module =  
      new SimpleModule("MyMapKeySerializerModule",  
          new Version(1, 0, 0, null));
    module.addKeySerializer(Date.class, new DateAsTimestampSerializer());

    MapType myMapType = TypeFactory.defaultInstance().constructMapType(HashMap.class, Date.class, String.class);

    ObjectWriter writer = new ObjectMapper().withModule(module).typedWriter(myMapType);
    System.out.println(writer.writeValueAsString(myMap));
    // {"1309806289240":"later","1309806289140":"now"}
  }
}

class DateAsTimestampSerializer extends JsonSerializer<Date>
{
  @Override
  public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) 
      throws IOException, JsonProcessingException
  {
    jgen.writeFieldName(String.valueOf(value.getTime()));
  }
}

Update for the latest Jackson (2.0.4):

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;

public class CustomKeySerializerDemo
{
  public static void main(String[] args) throws Exception
  {
    Map<Date, String> myMap = new HashMap<Date, String>();
    myMap.put(new Date(), "now");
    Thread.sleep(100);
    myMap.put(new Date(), "later");

    ObjectMapper mapper = new ObjectMapper();
    System.out.println(mapper.writeValueAsString(myMap));
    // {"2012-07-13T21:14:09.499+0000":"now","2012-07-13T21:14:09.599+0000":"later"}

    SimpleModule module = new SimpleModule();
    module.addKeySerializer(Date.class, new DateAsTimestampSerializer());

    MapType myMapType = TypeFactory.defaultInstance().constructMapType(HashMap.class, Date.class, String.class);

    ObjectWriter writer = new ObjectMapper().registerModule(module).writerWithType(myMapType);
    System.out.println(writer.writeValueAsString(myMap));
    // {"1342214049499":"now","1342214049599":"later"}
  }
}

class DateAsTimestampSerializer extends JsonSerializer<Date>
{
  @Override
  public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) 
      throws IOException, JsonProcessingException
  {
    jgen.writeFieldName(String.valueOf(value.getTime()));
  }
}

As usual, Bruce's answer is right on the spot.

One additional thought is that since there is a global setting for serializing Date values as timestamps:

SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS

Maybe that should apply here as well. And/or at least use standard ISO-8601 format for text. The main practical issue there is that of backwards compatibility; however, I doubt that current use of plain toString() is very useful as it is neither efficient nor convenient (to read back the value).

So if you want, you might want to file a feature request; this sounds like sub-optimal handling of Map keys by Jackson.