From a39efe2e2d98c8af2f3bcd5aae6b565906ba5a02 Mon Sep 17 00:00:00 2001 From: Benoit Lacelle Date: Wed, 3 May 2023 17:18:36 +0400 Subject: [PATCH] Prepare trying to have less exception when recursing in MapPath --- .../eu/solven/pepper/mappath/MapPath.java | 49 +++++++++++++++---- .../eu/solven/pepper/mappath/TestMapPath.java | 22 ++++++++- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/map-path/src/main/java/eu/solven/pepper/mappath/MapPath.java b/map-path/src/main/java/eu/solven/pepper/mappath/MapPath.java index 281fce67..b9a57831 100644 --- a/map-path/src/main/java/eu/solven/pepper/mappath/MapPath.java +++ b/map-path/src/main/java/eu/solven/pepper/mappath/MapPath.java @@ -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; @@ -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; @@ -226,7 +229,8 @@ private static NavigableMap 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("]"); @@ -245,12 +249,12 @@ private static void setJsonPath(DocumentContext context, String path, Object val // Remove the escape character '\' key = key.replaceAll("\\\\(?.)", "$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: \"" @@ -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 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); @@ -323,7 +327,10 @@ public static Map recurse(Map 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=" @@ -339,6 +346,30 @@ public static Map recurse(Map 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 split(String flatKey) { // TODO How can this be achieve with JsonPath own code? // CompiledPath path = (CompiledPath) PathCompiler.compile(flattenedKey); diff --git a/map-path/src/test/java/eu/solven/pepper/mappath/TestMapPath.java b/map-path/src/test/java/eu/solven/pepper/mappath/TestMapPath.java index 343a8da5..fd570f77 100644 --- a/map-path/src/test/java/eu/solven/pepper/mappath/TestMapPath.java +++ b/map-path/src/test/java/eu/solven/pepper/mappath/TestMapPath.java @@ -265,7 +265,6 @@ public void testOverIntermediateList_TrailingNull() { Assertions.assertThat(flatten).hasSize(2).containsEntry("$.k[0]", "a").containsEntry("$.k[1]", null); Map back = MapPath.recurse(flatten); - // The trailing null is removed Assertions.assertThat(back).isEqualTo(input); } @@ -277,7 +276,6 @@ public void testOverIntermediateList_LeadingNull() { Assertions.assertThat(flatten).hasSize(2).containsEntry("$.k[0]", null).containsEntry("$.k[1]", "a"); Map back = MapPath.recurse(flatten); - // The trailing null is removed Assertions.assertThat(back).isEqualTo(input); } @@ -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 input = ImmutableMap.of("k", + Arrays.asList(ImmutableMap.of("k", "a"), + ImmutableMap.of("k", "b"), + ImmutableMap.of("k", "c"), + ImmutableMap.of("k", "d"))); + NavigableMap 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 back = MapPath.recurse(flatten); + Assertions.assertThat(back).isEqualTo(input); + } }