Skip to content

Instantly share code, notes, and snippets.

@ejwinter
Last active February 16, 2017 17:50
Show Gist options
  • Select an option

  • Save ejwinter/23b94013d956c680afe69664496cdddb to your computer and use it in GitHub Desktop.

Select an option

Save ejwinter/23b94013d956c680afe69664496cdddb to your computer and use it in GitHub Desktop.
package edu.mayo.cim.mxd.fileloader;
import com.google.common.base.Splitter;
import com.google.common.collect.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This will take . (dot) delimitted fields and allow you to assemble them into a complex heirarchical map.
*
* Primary purpose is eventual serialization into JSON or XML for instance.
*
* PathMapBuilder builder = new PathMapBuilder()
* .add("a.b[0].d", "ab0d")
* .add("a.b[1].d", "ab1d")
* .add("a.b[1].e[0].g", "ab1e0g")
* .add("a.b[1].e[0].f", "ab1e0f")
* .add("a.x.z", "axz")
* .add("a.x.y.z", 42)
*
* Would produce the following:
*
* [a:[b:[[d:ab0d], [d:ab1d, e:[[f:ab1e0f, g:ab1e0g]]]], x:[y:[z:42], z:axz]]]
*/
public class PathMapBuilder {
private final SortedMap<String, Object> pathmap = Maps.newTreeMap();
private final Splitter pathSplitter;
private static final Pattern ARRAY_EXTRACTOR = Pattern.compile("(.*)\\[(\\d+)\\]");
public PathMapBuilder(){
pathSplitter = Splitter.on('.');
}
public synchronized PathMapBuilder add(String key, Object value){
pathmap.put(key,value);
return this;
}
public synchronized ImmutableMap<String,Object> build() {
final Map<String,Object> builder = Maps.newHashMap();
for (Map.Entry<String, Object> stringObjectEntry : pathmap.entrySet()) {
Map<String,Object> valueHolder = builder;
final List<String> splitPath = pathSplitter.splitToList(stringObjectEntry.getKey());
for (int i = 0; i < splitPath.size(); i++) {
final String currentPath = splitPath.get(i);
final Matcher arrayMatcher = ARRAY_EXTRACTOR.matcher(currentPath);
if(arrayMatcher.matches()){
String arrayName = arrayMatcher.group(1);
int arrayIndex = Integer.valueOf(arrayMatcher.group(2));
List<Object> array;
if(valueHolder.containsKey(arrayName)){
array = (List<Object>)valueHolder.get(arrayName);
}else{
array = Lists.newLinkedList();
valueHolder.put(arrayName, array);
}
if(i == splitPath.size()-1){
array.add(stringObjectEntry.getValue());
}else{
while(array.size() < arrayIndex+1){
array.add(Maps.newHashMap());
}
valueHolder = (Map)array.get(arrayIndex);
}
}else if(i == splitPath.size()-1){
valueHolder.put(currentPath, stringObjectEntry.getValue());
}else{
if(!valueHolder.containsKey(currentPath)){
valueHolder.put(currentPath, Maps.newHashMap());
}
Object pathObject = valueHolder.get(currentPath);
if(pathObject instanceof Map) {
valueHolder = (Map<String, Object>) pathObject;
}else{
throw new UnsupportedOperationException("We expect non terminal entries to be objects.");
}
}
}
}
return ImmutableMap.copyOf(builder);
}
}
package edu.mayo.cim.mxd.fileloader
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.collect.ImmutableMap
import spock.lang.Specification
/**
* Created by ejwinter on 2/16/17.
*/
class PathMapBuilderSpec extends Specification {
def "we can use it to fill in a complext tree of values"(){
given:
PathMapBuilder builder = new PathMapBuilder()
.add("a.b.c","abc")
.add("a.b.d", "abd")
.add("a.x.z", "axz")
.add("a.x.y.z", "axyz")
when:
ImmutableMap<String,Object> built = builder.build()
then:
Map<String,Object> a = built.get("a")
Map<String,Object> x = a.get("x")
Map<String,Object> y = x.get("y")
y.get("z") == "axyz"
}
def "it can hold various data types"(){
given:
PathMapBuilder builder = new PathMapBuilder()
.add("a.b.c","abc")
.add("a.b.d", "abd")
.add("a.x.z", "axz")
.add("a.x.y.z", 42)
when:
ImmutableMap<String,Object> built = builder.build()
then:
Map<String,Object> a = built.get("a")
Map<String,Object> x = a.get("x")
Map<String,Object> y = x.get("y")
y.get("z") == 42
}
def "it can be mapped to json"(){
given:
PathMapBuilder builder = new PathMapBuilder()
.add("a.b.c","abc")
.add("a.x.y.z", 42)
when:
ImmutableMap<String,Object> built = builder.build()
then:
new ObjectMapper().writeValueAsString(built) == "{\"a\":{\"b\":{\"c\":\"abc\"},\"x\":{\"y\":{\"z\":42}}}}"
}
def "it can handle lists"(){
given:
PathMapBuilder builder = new PathMapBuilder()
.add("a.b[0].d", "ab0d")
.add("a.b[1].d", "ab1d")
.add("a.b[1].e[0].g", "ab1e0g")
.add("a.b[1].e[0].f", "ab1e0f")
.add("a.x.z", "axz")
.add("a.x.y.z", 42)
when:
ImmutableMap<String,Object> built = builder.build()
then:
Map<String,Object> a = built.get("a")
Map<String,Object> b0 = a.get("b")[0]
b0.get("d") == "ab0d"
Map<String,Object> b1 = a.get("b")[1]
b1.get("d") == "ab1d"
and:
new ObjectMapper().writeValueAsString(built) == "{\"a\":{\"b\":[{\"d\":\"ab0d\"},{\"d\":\"ab1d\",\"e\":[{\"f\":\"ab1e0f\",\"g\":\"ab1e0g\"}]}],\"x\":{\"y\":{\"z\":42},\"z\":\"axz\"}}}"
}
}
@ejwinter
Copy link
Author

This was a handy utility class used when we tried to turn a table with headers and row values into complex json objects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment