001package org.gbif.api.jackson;
002
003import java.io.IOException;
004import java.util.AbstractMap;
005import java.util.Map;
006
007import org.codehaus.jackson.JsonGenerator;
008import org.codehaus.jackson.JsonParser;
009import org.codehaus.jackson.JsonProcessingException;
010import org.codehaus.jackson.map.DeserializationContext;
011import org.codehaus.jackson.map.JsonDeserializer;
012import org.codehaus.jackson.map.JsonSerializer;
013import org.codehaus.jackson.map.SerializerProvider;
014
015/**
016 * Jackson Serializer and Deserializer for {@link java.util.Map.Entry}.
017 * This is mostly for pre 2.7 version of Jackson see
018 * https://github.com/fasterxml/jackson-databind/issues/565
019 *
020 * The goal is to omit the key/value field name since they are implicit
021 * for a Map.Entry.
022 *
023 * {"key":"mykey","value":18} becomes {"mykey":18}
024 *
025 * The key will use toString() and the value can only be a String or a Number (int or float) for now.
026 *
027 * <pre>
028 * {@code
029 * // Usage for lists:
030 * @JsonSerialize(contentUsing = MapEntrySerde.MapEntryJsonSerializer.class)
031 * public List<Map.Entry<String, Object>> getKeyValueList() { ... }
032 * }
033 * </pre>
034 */
035public class MapEntrySerde {
036
037  public static class MapEntryJsonSerializer extends JsonSerializer<Map.Entry<Object, Object>> {
038
039    @Override
040    public void serialize(Map.Entry<Object, Object> value, JsonGenerator jgen,
041                          SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
042      if(value == null){
043        jgen.writeNull();
044        return;
045      }
046      jgen.writeStartObject();
047      jgen.writeFieldName(value.getKey().toString());
048      jgen.writeObject(value.getValue());
049      jgen.writeEndObject();
050    }
051  }
052
053  public static class MapEntryJsonDeserializer extends JsonDeserializer<Map.Entry<Object, Object>> {
054    @Override
055    public Map.Entry<Object, Object> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
056      String tmp = jp.getText(); // {
057      jp.nextToken();
058      String key = jp.getText();
059      jp.nextToken();
060      Object value;
061
062      switch (jp.getCurrentToken()) {
063        case VALUE_STRING: value = jp.getText();
064          break;
065        case VALUE_NUMBER_INT: value = jp.getIntValue();
066          break;
067        case VALUE_NUMBER_FLOAT: value = jp.getFloatValue();
068          break;
069        default :
070          throw ctxt.mappingException("Expected String or Number");
071      }
072      jp.nextToken();
073      tmp = jp.getText(); // }
074
075      return new AbstractMap.SimpleImmutableEntry<>(key, value);
076    }
077  }
078}