Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 40 additions & 9 deletions map-path/src/main/java/eu/solven/pepper/mappath/MapPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -37,6 +38,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
Expand Down Expand Up @@ -226,7 +229,8 @@ private static NavigableMap<String, Object> innerFlatten(boolean root,
private static void setJsonPath(DocumentContext context, String path, Object value) {
String parentPath;
String key;
int propertyIndex;
// Integer.MIN_VALUE in case of Map
int arrayIndex;
// parse the path ending
boolean endsWithBracket = path.endsWith("]");

Expand All @@ -245,12 +249,12 @@ private static void setJsonPath(DocumentContext context, String path, Object val
// Remove the escape character '\'
key = key.replaceAll("\\\\(?<escaped>.)", "$1");

propertyIndex = Integer.MIN_VALUE;
arrayIndex = Integer.MIN_VALUE;
} else {
// A path like `$.k[7]`
key = path.substring(pos + 1, path.length() - 1);
try {
propertyIndex = Integer.parseInt(key);
arrayIndex = Integer.parseInt(key);
} catch (NumberFormatException e) {
String msg = "Unsupported value \"" + key
+ "\" for index, only non-negative integers are expected; path: \""
Expand All @@ -268,19 +272,19 @@ private static void setJsonPath(DocumentContext context, String path, Object val
}
parentPath = path.substring(0, pos);
key = path.substring(pos + 1);
propertyIndex = Integer.MIN_VALUE;
arrayIndex = Integer.MIN_VALUE;
}
ensureParentExists(context, parentPath, propertyIndex);
ensureParentExists(context, parentPath, arrayIndex);

// set the value
if (propertyIndex == Integer.MIN_VALUE) {
if (arrayIndex == Integer.MIN_VALUE) {
context.put(parentPath, key, value);
} else {
List<Object> parent = context.read(parentPath);
if (propertyIndex < parent.size()) {
if (arrayIndex < parent.size()) {
context.set(path, value);
} else {
for (int i = parent.size(); i < propertyIndex; i++) {
for (int i = parent.size(); i < arrayIndex; i++) {
parent.add(null);
}
parent.add(value);
Expand Down Expand Up @@ -323,7 +327,10 @@ public static Map<String, Object> recurse(Map<String, ?> flatten) {

DocumentContext emptyJson = JsonPath.using(conf).parse("{}");

flatten.forEach((k, v) -> {
flatten.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey(), MapPath::orderFlatKey)).forEach(e -> {
String k = e.getKey();
Object v = e.getValue();

if (v instanceof List<?> || v instanceof Map<?, ?>) {
throw new IllegalArgumentException(
"A flatten Map should neither have a Map nor Collection value. value="
Expand All @@ -339,6 +346,30 @@ public static Map<String, Object> recurse(Map<String, ?> flatten) {
return emptyJson.json();
}

// This is useful to process flatten keys in an optimal order
// We consider an optimal order an order which prevents as many exception as possible when ensuring parentPath
// existence. Hence, the goal here is to consider first a key mapping to an array position with the highest possible
// position
private static int orderFlatKey(String left, String right) {
if (left.equals(right)) {
return 0;
}
String commonPrefix = Strings.commonPrefix(left, right);

int quoteIndex = CharMatcher.inRange('0', '9').negate().lastIndexIn(commonPrefix);
if (quoteIndex < 0) {
return left.compareTo(right);
} else if (commonPrefix.charAt(quoteIndex) != '\'') {
// These 2 pathes are not differing just by an array index
return left.compareTo(right);
}

String leftAfterCommon = left.substring(commonPrefix.length());
String rightAfterCommon = right.substring(commonPrefix.length());

return 0;
}

public static List<Object> split(String flatKey) {
// TODO How can this be achieve with JsonPath own code?
// CompiledPath path = (CompiledPath) PathCompiler.compile(flattenedKey);
Expand Down
22 changes: 20 additions & 2 deletions map-path/src/test/java/eu/solven/pepper/mappath/TestMapPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ public void testOverIntermediateList_TrailingNull() {
Assertions.assertThat(flatten).hasSize(2).containsEntry("$.k[0]", "a").containsEntry("$.k[1]", null);

Map<String, Object> back = MapPath.recurse(flatten);
// The trailing null is removed
Assertions.assertThat(back).isEqualTo(input);
}

Expand All @@ -277,7 +276,6 @@ public void testOverIntermediateList_LeadingNull() {
Assertions.assertThat(flatten).hasSize(2).containsEntry("$.k[0]", null).containsEntry("$.k[1]", "a");

Map<String, Object> back = MapPath.recurse(flatten);
// The trailing null is removed
Assertions.assertThat(back).isEqualTo(input);
}

Expand Down Expand Up @@ -428,4 +426,24 @@ public void testFlatten_mapArrayMapComplexMap() {

Assertions.assertThat(flatten).containsEntry("$.k1[0].k2_suffix.k3", "v").hasSize(1);
}

@Test
public void testList_reversedOrder() {
Map<String, ?> input = ImmutableMap.of("k",
Arrays.asList(ImmutableMap.of("k", "a"),
ImmutableMap.of("k", "b"),
ImmutableMap.of("k", "c"),
ImmutableMap.of("k", "d")));
NavigableMap<String, Object> flatten = MapPath.flatten(input);

Assertions.assertThat(flatten)
.containsEntry("$.k[0].k", "a")
.containsEntry("$.k[1].k", "b")
.containsEntry("$.k[2].k", "c")
.containsEntry("$.k[3].k", "d")
.hasSize(4);

Map<String, Object> back = MapPath.recurse(flatten);
Assertions.assertThat(back).isEqualTo(input);
}
}