diff --git a/README.md b/README.md index 600465d..31ee50f 100644 --- a/README.md +++ b/README.md @@ -233,9 +233,23 @@ System.out.println(compactJson); {"username": "john_doe", "address": {"street": "123 Main St", "city": "Springfield"}, "orders": [{"products": [{"name": "Laptop"}, {"name": "Mouse"}]}]} ``` +## Map objects +Convert any object to a Map representation using the `toMap` method. This is useful for scenarios where you need a visual representation of the entire object structure. + +```java +Map userMap = dotPathQL.toMap(userObject); +``` + ## Helper Utilities -You can also easy access the map result using the `DotPathQL` utility methods: +You can also easy access the map result using the `DotUtils` utility methods: + +### Path parsing + +```java +List paths = DotUtils.parsePaths("locations[home.street,work.city],contact[email,phone.mobile],age"); +// Result: ["locations[home.street,work.city]", "contact[email,phone.mobile]", "age"] +``` ### Quick Access Methods @@ -249,15 +263,9 @@ Map result = dotPathQL.filter(userObject, List.of( )); // Step 2: Accessing the result -Map address = dotPathQL.mapFrom(result, "address"); -List> friendList = dotPathQL.listFrom(result, "friendList"); -Object[] games = dotPathQL.arrayFrom(result, "games"); -``` - -### Map objects - -```java -Map userMap = dotPathQL.toMap(userObject); +Map address = DotUtils.mapFrom(result, "address"); +List> friendList = DotUtils.listFrom(result, "friendList"); +Object[] games = DotUtils.arrayFrom(result, "games"); ``` ## Technical Requirements diff --git a/src/main/java/io/github/trackerforce/DotPathQL.java b/src/main/java/io/github/trackerforce/DotPathQL.java index 411090e..26fefaa 100644 --- a/src/main/java/io/github/trackerforce/DotPathQL.java +++ b/src/main/java/io/github/trackerforce/DotPathQL.java @@ -14,7 +14,6 @@ * @author petruki * @since 2025-08-02 */ -@SuppressWarnings("unchecked") public class DotPathQL { private final DotPath pathFilter; @@ -59,65 +58,6 @@ public Map exclude(T source, List excludePaths) { return pathExclude.run(source, excludePaths); } - /** - * Converts the source object to a map representation. - * - * @param the type of the source object - * @param source the source object to convert - * @return a map containing all properties of the source object - */ - public Map toMap(T source) { - return exclude(source, Collections.emptyList()); - } - - /** - * Extracts a map from the source map based on the specified property. - * - * @param source the source map - * @param property the property to extract - * @return the extracted map or an empty map if not found - * @throws ClassCastException if the property is not a map - */ - public Map mapFrom(Map source, String property) { - if (isInvalid(source, property)) { - return Collections.emptyMap(); - } - - return (Map) source.get(property); - } - - /** - * Extracts a list of maps from the source map based on the specified property. - * - * @param source the source map - * @param property the property to extract - * @return the extracted list of maps or an empty list if not found - * @throws ClassCastException if the property is not a list of maps - */ - public List> listFrom(Map source, String property) { - if (isInvalid(source, property)) { - return Collections.emptyList(); - } - - return (List>) source.get(property); - } - - /** - * Extracts a list of objects from the source map based on the specified property. - * - * @param source the source map - * @param property the property to extract - * @return the extracted list of objects or an empty list if not found - * @throws ClassCastException if the property is not a list of objects - */ - public Object[] arrayFrom(Map source, String property) { - if (isInvalid(source, property)) { - return new Object[0]; - } - - return (Object[]) source.get(property); - } - /** * Adds default filter paths that will be included in every filtering operation. * @@ -136,6 +76,17 @@ public void addDefaultExcludePaths(List paths) { pathExclude.addDefaultPaths(paths); } + /** + * Converts the source object to a map representation. + * + * @param the type of the source object + * @param source the source object to convert + * @return a map containing all properties of the source object + */ + public Map toMap(T source) { + return exclude(source, Collections.emptyList()); + } + /** * Converts the given sourceMap to a JSON string representation with optional formatting. * @@ -159,7 +110,4 @@ public String toJson(Map sourceMap, boolean prettier) { return pathPrinter.toJson(sourceMap, prettier); } - private boolean isInvalid(Map source, String property) { - return source == null || property == null || property.isEmpty() || !source.containsKey(property); - } } diff --git a/src/main/java/io/github/trackerforce/DotUtils.java b/src/main/java/io/github/trackerforce/DotUtils.java new file mode 100644 index 0000000..6386ec1 --- /dev/null +++ b/src/main/java/io/github/trackerforce/DotUtils.java @@ -0,0 +1,80 @@ +package io.github.trackerforce; + +import io.github.trackerforce.path.DotPathFactory; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Utility class to complement the DotPathQL functionalities. + */ +@SuppressWarnings("unchecked") +public class DotUtils { + + private DotUtils() { } + + /** + * Parses a dot-notated path into its components. + * + * @param path the dot-notated path + * @return a list of path components + */ + public static List parsePaths(String path) { + return DotPathFactory.buildParser().parse(path); + } + + /** + * Extracts a map from the source map based on the specified property. + * + * @param source the source map + * @param property the property to extract + * @return the extracted map or an empty map if not found + * @throws ClassCastException if the property is not a map + */ + public static Map mapFrom(Map source, String property) { + if (isInvalid(source, property)) { + return Collections.emptyMap(); + } + + return (Map) source.get(property); + } + + /** + * Extracts a list of maps from the source map based on the specified property. + * + * @param source the source map + * @param property the property to extract + * @return the extracted list of maps or an empty list if not found + * @throws ClassCastException if the property is not a list of maps + */ + public static List> listFrom(Map source, String property) { + if (isInvalid(source, property)) { + return Collections.emptyList(); + } + + return (List>) source.get(property); + } + + /** + * Extracts a list of objects from the source map based on the specified property. + * + * @param source the source map + * @param property the property to extract + * @return the extracted list of objects or an empty list if not found + * @throws ClassCastException if the property is not a list of objects + */ + public static Object[] arrayFrom(Map source, String property) { + if (isInvalid(source, property)) { + return new Object[0]; + } + + return (Object[]) source.get(property); + } + + private static boolean isInvalid(Map source, String property) { + return source == null || property == null || property.isEmpty() || !source.containsKey(property); + } +} + + diff --git a/src/main/java/io/github/trackerforce/path/DotPathFactory.java b/src/main/java/io/github/trackerforce/path/DotPathFactory.java index 2ebcd41..41471e6 100644 --- a/src/main/java/io/github/trackerforce/path/DotPathFactory.java +++ b/src/main/java/io/github/trackerforce/path/DotPathFactory.java @@ -1,5 +1,6 @@ package io.github.trackerforce.path; +import io.github.trackerforce.path.api.DotParse; import io.github.trackerforce.path.api.DotPath; import io.github.trackerforce.path.api.DotPrinter; @@ -37,4 +38,13 @@ public static DotPath buildExclude() { public static DotPrinter buildPrinter(int indentSize) { return new PathPrinter(indentSize); } + + /** + * Builds and returns a new instance of PathParser. + * + * @return a new PathParser instance + */ + public static DotParse buildParser() { + return new PathParser(); + } } diff --git a/src/main/java/io/github/trackerforce/path/PathParser.java b/src/main/java/io/github/trackerforce/path/PathParser.java new file mode 100644 index 0000000..5f30227 --- /dev/null +++ b/src/main/java/io/github/trackerforce/path/PathParser.java @@ -0,0 +1,56 @@ +package io.github.trackerforce.path; + +import io.github.trackerforce.path.api.DotParse; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Parser for dot notation paths with support for nested bracket expressions. + */ +public class PathParser implements DotParse { + + /** + * Parses a dot notation string into a list of paths, handling nested brackets correctly. + * + * @param input the dot notation string to parse + * @return list of parsed paths + */ + @Override + public List parse(String input) { + if (input == null || input.trim().isEmpty()) { + return Collections.emptyList(); + } + + List paths = new ArrayList<>(); + StringBuilder currentPath = new StringBuilder(); + int bracketDepth = 0; + + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + + if (c == '[') { + bracketDepth++; + currentPath.append(c); + } else if (c == ']') { + bracketDepth--; + currentPath.append(c); + } else if (c == ',' && bracketDepth == 0) { + // Only split on comma when not inside brackets + if (!currentPath.isEmpty()) { + paths.add(currentPath.toString().trim()); + currentPath = new StringBuilder(); + } + } else { + currentPath.append(c); + } + } + + // Add the last path if there's content + if (!currentPath.isEmpty()) { + paths.add(currentPath.toString().trim()); + } + + return paths; + } +} diff --git a/src/main/java/io/github/trackerforce/path/api/DotParse.java b/src/main/java/io/github/trackerforce/path/api/DotParse.java new file mode 100644 index 0000000..7f24617 --- /dev/null +++ b/src/main/java/io/github/trackerforce/path/api/DotParse.java @@ -0,0 +1,17 @@ +package io.github.trackerforce.path.api; + +import java.util.List; + +/** + * Interface for parsing dot path strings into a list of path segments. + */ +public interface DotParse { + + /** + * Parses a dot path string into a list of path segments. + * + * @param path the dot path string to parse + * @return a list of path segments extracted from the dot path + */ + List parse(String path); +} diff --git a/src/test/java/io/github/trackerforce/UtilsTest.java b/src/test/java/io/github/trackerforce/DotUtilsTest.java similarity index 55% rename from src/test/java/io/github/trackerforce/UtilsTest.java rename to src/test/java/io/github/trackerforce/DotUtilsTest.java index b77227b..d112d93 100644 --- a/src/test/java/io/github/trackerforce/UtilsTest.java +++ b/src/test/java/io/github/trackerforce/DotUtilsTest.java @@ -5,12 +5,12 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.util.List; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; -class UtilsTest { +class DotUtilsTest { DotPathQL dotPathQL = new DotPathQL(); @@ -26,7 +26,7 @@ static Stream userDetailProvider() { void shouldReturnSelectedArrayProperties(String implementation, Object userDetail) { // When var result = dotPathQL.toMap(userDetail); - var roles = dotPathQL.arrayFrom(result, "roles"); + var roles = DotUtils.arrayFrom(result, "roles"); // Then assertNotNull(roles); @@ -40,7 +40,7 @@ void shouldReturnSelectedArrayProperties(String implementation, Object userDetai void shouldReturnSelectedListProperties(String implementation, Object userDetail) { // When var result = dotPathQL.toMap(userDetail); - var occupations = dotPathQL.listFrom(result, "occupations"); + var occupations = DotUtils.listFrom(result, "occupations"); // Then assertNotNull(occupations); @@ -52,7 +52,7 @@ void shouldReturnSelectedListProperties(String implementation, Object userDetail void shouldReturnSelectedMapProperties(String implementation, Object userDetail) { // When var result = dotPathQL.toMap(userDetail); - var locations = dotPathQL.mapFrom(result, "locations"); + var locations = DotUtils.mapFrom(result, "locations"); // Then assertNotNull(locations); @@ -62,7 +62,7 @@ void shouldReturnSelectedMapProperties(String implementation, Object userDetail) @Test void shouldReturnEmptyArrayWhenPropertyNotExists() { // When - var unknown = dotPathQL.arrayFrom(null, "unknown"); + var unknown = DotUtils.arrayFrom(null, "unknown"); // Then assertNotNull(unknown); @@ -72,7 +72,7 @@ void shouldReturnEmptyArrayWhenPropertyNotExists() { @Test void shouldReturnEmptyListWhenPropertyNotExists() { // When - var unknown = dotPathQL.listFrom(null, "unknown"); + var unknown = DotUtils.listFrom(null, "unknown"); // Then assertNotNull(unknown); @@ -82,10 +82,53 @@ void shouldReturnEmptyListWhenPropertyNotExists() { @Test void shouldReturnEmptyMapWhenPropertyNotExists() { // When - var unknown = dotPathQL.mapFrom(null, "unknown"); + var unknown = DotUtils.mapFrom(null, "unknown"); // Then assertNotNull(unknown); assertEquals(0, unknown.size()); } + + static Stream parsePathsArgs() { + return Stream.of( + Arguments.of("locations.home,locations.work", List.of( + "locations.home", + "locations.work" + )), + Arguments.of("locations,address.street,orders[product.name]", List.of( + "locations", + "address.street", + "orders[product.name]" + )), + Arguments.of("locations[home.street,work.city]", List.of( + "locations[home.street,work.city]" + )), + Arguments.of("locations[home[street,city]],locations[work[city]]", List.of( + "locations[home[street,city]]", + "locations[work[city]]" + )) + ); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("parsePathsArgs") + void shouldParsePathsIntoList(String input, List expected) { + // When + var paths = DotUtils.parsePaths(input); + + // Then + assertNotNull(paths); + assertEquals(expected.size(), paths.size()); + assertArrayEquals(expected.toArray(), paths.toArray()); + } + + @Test + void shouldReturnEmptyListWhenParseInvalidPath() { + // When + var paths = DotUtils.parsePaths(null); + + // Then + assertNotNull(paths); + assertTrue(paths.isEmpty()); + } } diff --git a/src/test/java/io/github/trackerforce/ExcludeTypeClassRecordTest.java b/src/test/java/io/github/trackerforce/ExcludeTypeClassRecordTest.java index acae66b..8841581 100644 --- a/src/test/java/io/github/trackerforce/ExcludeTypeClassRecordTest.java +++ b/src/test/java/io/github/trackerforce/ExcludeTypeClassRecordTest.java @@ -28,7 +28,7 @@ void shouldExcludeSimpleNestedField(String implementation, Object userDetail) { var result = dotPathQL.exclude(userDetail, List.of("orders.orderId")); // Then - var orders = dotPathQL.listFrom(result, "orders"); + var orders = DotUtils.listFrom(result, "orders"); assertNotNull(orders); assertEquals(2, orders.size()); assertFalse(orders.get(0).containsKey("orderId")); @@ -51,7 +51,7 @@ void shouldExcludeMultipleSameBranch(String implementation, Object userDetail) { )); // Then - var address = dotPathQL.mapFrom(result, "address"); + var address = DotUtils.mapFrom(result, "address"); assertNotNull(address); assertFalse(address.containsKey("street")); assertFalse(address.containsKey("city")); @@ -70,18 +70,18 @@ void shouldExcludeMultipleDifferentBranches(String implementation, Object userDe )); // Then - var address = dotPathQL.mapFrom(result, "address"); + var address = DotUtils.mapFrom(result, "address"); assertNotNull(address); assertFalse(address.containsKey("street")); assertTrue(address.containsKey("city")); // orders products have no description - var orders = dotPathQL.listFrom(result, "orders"); - var firstOrderProducts = dotPathQL.listFrom(orders.get(0), "products"); + var orders = DotUtils.listFrom(result, "orders"); + var firstOrderProducts = DotUtils.listFrom(orders.get(0), "products"); assertFalse(firstOrderProducts.get(0).containsKey("description")); // additionalInfo without lastLogin - var addInfo = dotPathQL.mapFrom(result, "additionalInfo"); + var addInfo = DotUtils.mapFrom(result, "additionalInfo"); assertNotNull(addInfo); assertFalse(addInfo.containsKey("lastLogin")); assertEquals("English", addInfo.get("preferredLanguage")); @@ -94,15 +94,15 @@ void shouldExcludeGroupedPaths(String implementation, Object userDetail) { var result = dotPathQL.exclude(userDetail, List.of("locations[home.street,work.city]")); // Then - var locations = dotPathQL.mapFrom(result, "locations"); + var locations = DotUtils.mapFrom(result, "locations"); assertNotNull(locations); - var home = dotPathQL.mapFrom(locations, "home"); + var home = DotUtils.mapFrom(locations, "home"); assertNotNull(home); assertFalse(home.containsKey("street")); assertTrue(home.containsKey("city")); - var work = dotPathQL.mapFrom(locations, "work"); + var work = DotUtils.mapFrom(locations, "work"); assertFalse(work.containsKey("city")); } @@ -115,7 +115,7 @@ void shouldAddDefaultExclusionPaths(String implementation, Object userDetail) { // Then assertFalse(result.containsKey("username")); - var address = dotPathQL.mapFrom(result, "address"); + var address = DotUtils.mapFrom(result, "address"); assertNotNull(address); assertFalse(address.containsKey("city")); assertTrue(address.containsKey("street")); diff --git a/src/test/java/io/github/trackerforce/ExcludeTypeClassTest.java b/src/test/java/io/github/trackerforce/ExcludeTypeClassTest.java index f05228f..d42f89d 100644 --- a/src/test/java/io/github/trackerforce/ExcludeTypeClassTest.java +++ b/src/test/java/io/github/trackerforce/ExcludeTypeClassTest.java @@ -20,10 +20,10 @@ void shouldExcludeAndNotReturnPrivateAttributes() { var result = dotPathQL.exclude(customer, List.of("metadata.tags")); // Then - var metadata = dotPathQL.mapFrom(result, "metadata"); + var metadata = DotUtils.mapFrom(result, "metadata"); assertEquals(0, metadata.size()); // No fields should remain after excluding tags and private password - var features = dotPathQL.listFrom(result, "features"); + var features = DotUtils.listFrom(result, "features"); assertEquals(2, features.size()); assertTrue(features.get(0).containsKey("isEnabled")); // checking boolean field } diff --git a/src/test/java/io/github/trackerforce/FilterTypeClassRecordTest.java b/src/test/java/io/github/trackerforce/FilterTypeClassRecordTest.java index 8333c44..8ea4878 100644 --- a/src/test/java/io/github/trackerforce/FilterTypeClassRecordTest.java +++ b/src/test/java/io/github/trackerforce/FilterTypeClassRecordTest.java @@ -36,12 +36,12 @@ void shouldReturnFilteredObjectAttributes(String implementation, Object userDeta assertEquals("john_doe", result.get("username")); // Verify nested address structure - var address = dotPathQL.mapFrom(result, "address"); + var address = DotUtils.mapFrom(result, "address"); assertNotNull(address); assertEquals("123 Main St", address.get("street")); // Verify nested orders structure with products - var orders = dotPathQL.listFrom(result, "orders"); + var orders = DotUtils.listFrom(result, "orders"); assertNotNull(orders); assertEquals(2, orders.size()); @@ -49,7 +49,7 @@ void shouldReturnFilteredObjectAttributes(String implementation, Object userDeta var firstOrder = orders.get(0); // Verify first order with its products - var firstOrderProducts = dotPathQL.listFrom(firstOrder, "products"); + var firstOrderProducts = DotUtils.listFrom(firstOrder, "products"); assertNotNull(firstOrderProducts, "Products list should not be null"); assertEquals(2, firstOrderProducts.size()); assertEquals("Laptop", firstOrderProducts.get(0).get("name")); @@ -57,7 +57,7 @@ void shouldReturnFilteredObjectAttributes(String implementation, Object userDeta // Verify second order with its product var secondOrder = orders.get(1); - var secondOrderProducts = dotPathQL.listFrom(secondOrder, "products"); + var secondOrderProducts = DotUtils.listFrom(secondOrder, "products"); assertEquals(1, secondOrderProducts.size()); assertEquals("Headphones", secondOrderProducts.get(0).get("name")); } @@ -72,7 +72,7 @@ void shouldReturnFilteredObjectWithArray(String implementation, Object userDetai assertEquals(1, result.size()); // Verify occupations structure - var occupations = dotPathQL.listFrom(result, "occupations"); + var occupations = DotUtils.listFrom(result, "occupations"); assertEquals(2, occupations.size()); assertEquals("Software Engineer", occupations.get(0).get("title")); assertEquals("Project Manager", occupations.get(1).get("title")); @@ -91,7 +91,7 @@ void shouldReturnFilteredObjectWithMap(String implementation, Object userDetail) assertEquals(1, result.size()); // Verify additionalInfo structure - var additionalInfo = dotPathQL.mapFrom(result, "additionalInfo"); + var additionalInfo = DotUtils.mapFrom(result, "additionalInfo"); assertNotNull(additionalInfo); assertEquals("English", additionalInfo.get("preferredLanguage")); assertEquals("Active", additionalInfo.get("subscriptionStatus")); @@ -109,16 +109,16 @@ void shouldReturnFilteredObjectWithComplexMap(String implementation, Object user // Then assertEquals(1, result.size()); - var locations = dotPathQL.mapFrom(result, "locations"); + var locations = DotUtils.mapFrom(result, "locations"); assertEquals(2, locations.size()); // Verify home address structure - var homeLocation = dotPathQL.mapFrom(locations, "home"); + var homeLocation = DotUtils.mapFrom(locations, "home"); assertNotNull(homeLocation); assertEquals("456 Elm St", homeLocation.get("street")); // Verify work address structure - var workLocation = dotPathQL.mapFrom(locations, "work"); + var workLocation = DotUtils.mapFrom(locations, "work"); assertNotNull(workLocation); assertEquals("Springfield", workLocation.get("city")); } @@ -133,8 +133,8 @@ void shouldAddDefaultFilterPaths(String implementation, Object userDetail) { // Then assertEquals(2, result.size()); assertEquals("john_doe", result.get("username")); - assertEquals(1, dotPathQL.mapFrom(result, "address").size()); - assertEquals("Springfield", dotPathQL.mapFrom(result, "address").get("city")); + assertEquals(1, DotUtils.mapFrom(result, "address").size()); + assertEquals("Springfield", DotUtils.mapFrom(result, "address").get("city")); } @ParameterizedTest(name = "{0}") @@ -146,16 +146,16 @@ void shouldReturnFilteredObjectUsingGroupedPaths(String implementation, Object u // Then assertEquals(1, result.size()); - var locations = dotPathQL.mapFrom(result, "locations"); + var locations = DotUtils.mapFrom(result, "locations"); assertEquals(2, locations.size()); // Verify home address structure - var homeLocation = dotPathQL.mapFrom(locations, "home"); + var homeLocation = DotUtils.mapFrom(locations, "home"); assertNotNull(homeLocation); assertEquals("456 Elm St", homeLocation.get("street")); // Verify work address structure - var workLocation = dotPathQL.mapFrom(locations, "work"); + var workLocation = DotUtils.mapFrom(locations, "work"); assertNotNull(workLocation); assertEquals("Springfield", workLocation.get("city")); } @@ -169,17 +169,17 @@ void shouldReturnFilteredObjectUsingNestedGroupedPaths(String implementation, Ob // Then assertEquals(1, result.size()); - var locations = dotPathQL.mapFrom(result, "locations"); + var locations = DotUtils.mapFrom(result, "locations"); assertEquals(2, locations.size()); // Verify home address structure - var homeLocation = dotPathQL.mapFrom(locations, "home"); + var homeLocation = DotUtils.mapFrom(locations, "home"); assertNotNull(homeLocation); assertEquals("456 Elm St", homeLocation.get("street")); assertEquals("Springfield", homeLocation.get("city")); // Verify work address structure - var workLocation = dotPathQL.mapFrom(locations, "work"); + var workLocation = DotUtils.mapFrom(locations, "work"); assertNotNull(workLocation); assertEquals("Springfield", workLocation.get("city")); } diff --git a/src/test/java/io/github/trackerforce/FilterTypeClassTest.java b/src/test/java/io/github/trackerforce/FilterTypeClassTest.java index 7f6d4fe..660ed04 100644 --- a/src/test/java/io/github/trackerforce/FilterTypeClassTest.java +++ b/src/test/java/io/github/trackerforce/FilterTypeClassTest.java @@ -27,13 +27,13 @@ void shouldReturnFilteredObjectPrivateAttributes() { assertEquals(1, result.size()); // Verify metadata structure - var metadata = dotPathQL.mapFrom(result, "metadata"); + var metadata = DotUtils.mapFrom(result, "metadata"); assertNotNull(metadata); assertEquals("securePassword", metadata.get("password")); assertNotNull(metadata.get("tags")); - var tags = dotPathQL.arrayFrom(metadata, "tags"); + var tags = DotUtils.arrayFrom(metadata, "tags"); assertEquals(2, tags.length); assertEquals("tag1", tags[0]); assertEquals("tag2", tags[1]); @@ -50,15 +50,15 @@ void shouldReturnFilteredObjectListOfListAttributes() { // Then assertEquals(1, result.size()); assertNotNull(result.get("features")); - var features = dotPathQL.listFrom(result, "features"); + var features = DotUtils.listFrom(result, "features"); assertNotNull(features); assertEquals(1, features.size()); - var metadata = dotPathQL.listFrom(features.get(0), "metadata"); + var metadata = DotUtils.listFrom(features.get(0), "metadata"); assertNotNull(metadata); assertEquals(2, metadata.size()); - assertEquals("defaultTag", dotPathQL.arrayFrom(metadata.get(0), "tags")[0]); - assertEquals("anotherTag", dotPathQL.arrayFrom(metadata.get(1), "tags")[0]); + assertEquals("defaultTag", DotUtils.arrayFrom(metadata.get(0), "tags")[0]); + assertEquals("anotherTag", DotUtils.arrayFrom(metadata.get(1), "tags")[0]); } }