Created
August 24, 2015 20:01
-
-
Save martiell/e677c142518038f35379 to your computer and use it in GitHub Desktop.
Jackson serialization/deserialization of dot-separated property keys
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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