Florian Gutmann
Florian Gutmann

Reputation: 2756

Serializing Map<Date, String> with Jackson

I want to serialize a Map with Jackson. The Date should be serialized as a timestamp, like all my other dates.

The following code renders the keys in the form "Tue Mar 11 00:00:00 CET 1952" (which is Date.toString()) instead of the timestamp.

Map<Date, String> myMap = new HashMap<Date, String>();
...
ObjectMapper.writeValue(myMap)

I assume this is because of type erasure and jackson doesn't know at runtime that the key is a Date. But I didn't find a way to pass a TypeReference to any writeValue method.

Is there a simple way to achieve my desired behaviour or are all keys always rendered as Strings by jackson?

Thanks for any hint.

Upvotes: 38

Views: 33309

Answers (3)

Programmer Bruce
Programmer Bruce

Reputation: 66943

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()));
  }
}

Upvotes: 49

StaxMan
StaxMan

Reputation: 116512

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.

Upvotes: 5

Roben
Roben

Reputation: 848

Since Jackson 2.0 (maybe 1.9, too), WRITE_DATE_KEYS_AS_TIMESTAMPS can be used to change this particular behavior.

Usage example for ObjectMapper:

ObjectMapper m = new ObjectMapper().configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, true);

and for ObjectWriter:

ObjectWriter w = mapper.with(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);

Upvotes: 1

Related Questions