Skip to content

Instantly share code, notes, and snippets.

@martiell
Created August 24, 2015 20:01
Show Gist options
  • Save martiell/e677c142518038f35379 to your computer and use it in GitHub Desktop.
Save martiell/e677c142518038f35379 to your computer and use it in GitHub Desktop.
Jackson serialization/deserialization of dot-separated property keys
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.TreeMap;
import static java.util.Collections.list;
import static java.util.stream.Collectors.joining;
public class Configuration {
/**
* Converts a configuration object into a map of configuration values.
*/
static class ConfigurationDeserializer extends JsonDeserializer {
@Override
public Object deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException {
Map<String, String> map = new LinkedHashMap<>();
List<String> path = new ArrayList<>();
for (JsonToken token = parser.nextToken(); token != null; token = parser.nextToken()) {
if (token.equals(JsonToken.FIELD_NAME)) {
path.add(parser.getCurrentName());
} else if (token.isScalarValue()) {
String string = Objects.toString(parser.getValueAsString(), "");
map.put(path.stream().collect(joining(".")), string);
path.remove(path.size() - 1);
}
}
return map;
}
}
/**
* Converts a map of configuration values into a Jackson object.
*/
static class ConfigurationSerializer extends JsonSerializer<Map> {
@Override
public void serialize(Map map, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
// Build and traverse TreeMap to ensure keys are visited in sort order
Map<String, String> treeMap = new TreeMap<>(map);
List<String> prev = new ArrayList<>();
for (Map.Entry<String, String> entry : treeMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
Enumeration tokenizer = new StringTokenizer(key, ".");
List<String> segments = list(tokenizer);
List<String> curr = segments.subList(0, segments.size() - 1);
int prefix = commonPrefixLength(prev, curr);
for (int i = 0; i < prev.size() - prefix; i++) {
gen.writeEndObject();
}
for (int i = 0; i < curr.size() - prefix; i++) {
String fieldName = curr.get(prefix + i);
gen.writeObjectFieldStart(fieldName);
}
String name = segments.get(segments.size() - 1);
gen.writeStringField(name, String.valueOf(value));
prev = curr;
}
for (int i = 0; i < prev.size(); i++) {
gen.writeEndObject();
}
gen.writeEndObject();
}
private <T> int commonPrefixLength(List<T> prev, List<T> curr) {
int max = Math.min(curr.size(), prev.size());
int prefix = 0;
while (prefix < max && curr.get(prefix).equals(prev.get(prefix))) {
prefix++;
}
return prefix;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment