Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare trying to have less exception when recursing in MapPath #428

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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);
}
}