A powerful Java utility for dynamically filtering and extracting nested properties from complex objects using dot-notation paths. Perfect for API responses, data transformation, and selective object serialization.
The DotPathQL
is the core component of this project that allows you to extract specific properties from complex nested objects (including Records, POJOs, Collections, Arrays, and Maps) using simple dot-notation paths like "user.address.street"
or "orders.products.name"
.
- 🎯 Selective Property Extraction: Extract only the properties you need
- 🚫 Property Exclusion: Exclude specific properties and return everything else
- 🔐 Property Obfuscation: Replace sensitive property values with "****" while preserving structure
- 🔍 Deep Nested Support: Navigate through multiple levels of object nesting
- 📋 Collection Handling: Process Lists, Arrays, and other Collections
- 🗺️ Map Support: Handle both simple and complex Map structures
- 📝 Record & POJO Support: Works with Java Records and traditional classes
- 📄 JSON Output: Convert results to pretty-formatted or compact JSON strings
- 🔒 Private Field Access: Can access private fields when getters aren't available (filtering only)
- 🚀 Performance Optimized: Efficient reflection-based property access
Maven
<dependency>
<groupId>ca.trackerforce</groupId>
<artifactId>dot-path-ql</artifactId>
<version>${dot-path-ql.version}</version>
</dependency>
Gradle
implementation 'ca.trackerforce:dot-path-ql:${dot-path-ql.version}'
// Filter specific properties from an object
Map<String, Object> result = new DotPathQL().filter(userObject, List.of(
"username",
"address.street",
"address.city"
));
// Exclude specific properties and return everything else
Map<String, Object> result = new DotPathQL().exclude(userObject, List.of(
"password",
"ssn",
"address.country"
));
// Obfuscate specific properties by replacing their values with "****"
Map<String, Object> result = new DotPathQL().obfuscate(userObject, List.of(
"password",
"ssn",
"creditCard.number"
));
The pipeline feature allows you to chain multiple operations using a fluent API. Supports combining exclude
and obfuscate
operations:
DotPathQL dotPathQL = new DotPathQL();
// Chain exclude and obfuscate operations in a single pipeline
Map<String, Object> result = dotPathQL.pipeline(userObject)
.exclude(List.of("additionalInfo.lastLogin"))
.obfuscate(List.of("address.zipCode", "phoneNumber"))
.execute();
// Using default paths for exclude and obfuscate
dotPathQL.addDefaultExcludePaths(List.of("password"));
dotPathQL.addDefaultObfuscatePaths(List.of("ssn", "creditCard.number"));
Map<String, Object> result = dotPathQL.pipeline(userObject)
.exclude() // Uses default exclude paths
.obfuscate() // Uses default obfuscate paths
.execute();
- Fluent Interface: More readable and intuitive method chaining
- Single Execution: Apply multiple transformations in one operation
- Performance: Avoids multiple object traversals
- Consistency: Predictable processing order for combined operations
- Simple Properties (primitive and object types)
- Nested Objects
- Collections and Arrays
- Map Structures
- Grouped Paths
For scenarios where you need multiple properties from the same parent object, you can use the grouped paths syntax to write more concise filter expressions:
"parent[child1.property,child2.property,child3]"
List<String> paths = List.of(
"locations[home.street,work.city]"
);
The grouped paths are automatically expanded internally:
"locations[home.street,work.city]"
becomes"locations.home.street"
and"locations.work.city"
"contact[email,phone.mobile]"
becomes"contact.email"
and"contact.phone.mobile"
The utility uses a multi-layered approach to access object properties:
- Record Components (Most Efficient): For Java Records, uses the generated accessor methods
- Getter Methods: Tries standard getter methods (
getName()
,getAddress()
) - Direct Field Access: Falls back to direct field access for private fields (except for the exclude API)
- Collections/Arrays: Creates a list of maps, processing each element
- Maps: Handles both simple values and complex objects as Map values
- Objects: Recursively processes nested objects
- Path Resolution: Splits dot-notation paths and processes them hierarchically
Perfect for creating flexible APIs where clients can specify which fields they need:
import ca.trackerforce.DotUtils;
@GetMapping("/users/{id}")
public Map<String, Object> getUser(
@PathVariable Long id,
@RequestParam String paths
) {
User user = userService.findById(id);
return doPathQl.filter(user, DotUtils.parsePaths(paths));
}
Reduce payload size by extracting only required fields:
// Instead of sending full objects, send only what's needed
List<String> essentialFields = List.of("id", "name", "status");
List<Map<String, Object>> lightweightData = users.stream()
.map(user -> doPathQl.filter(user, essentialFields))
.collect(Collectors.toList());
Remove sensitive information while preserving the rest of the data structure:
// Exclude sensitive fields from user profiles
List<String> sensitiveFields = List.of(
"password",
"ssn",
"creditCard.number",
"address.country" // Remove specific nested fields
);
Map<String, Object> publicProfile = doPathQl.exclude(userObject, sensitiveFields);
Create APIs where clients can specify which fields to exclude:
@GetMapping("/users/{id}")
public Map<String, Object> getUser(
@PathVariable Long id,
@RequestParam(required = false) String exclude
) {
User user = userService.findById(id);
if (exclude != null && !exclude.isEmpty()) {
return doPathQl.exclude(user, DotUtils.parsePaths(exclude));
}
return doPathQl.toMap(user); // Return all fields
}
Extract specific data points for reports:
List<String> reportFields = List.of(
"customer.name",
"orders.total",
"orders.date",
"orders.products.category"
);
Mask sensitive information while maintaining data structure for logging, debugging, or sharing with third parties:
// Obfuscate sensitive fields while keeping the structure intact
List<String> sensitiveFields = List.of(
"password",
"ssn",
"creditCard.number",
"bankAccount.accountNumber",
"personalInfo.phoneNumber"
);
Map<String, Object> obfuscatedData = doPathQl.obfuscate(userObject, sensitiveFields);
// Result preserves structure but replaces sensitive values with "****"
// {
// "username": "john_doe",
// "password": "****",
// "ssn": "****",
// "creditCard": {
// "number": "****",
// "expiryDate": "12/25"
// },
// "email": "john@example.com"
// }
Convert your filtered or excluded results to JSON format using the built-in toJson
method. This feature supports both pretty-formatted (indented) and compact (single-line) output.
var dotPathQL = new DotPathQL();
var result = dotPathQL.filter(userObject, List.of(
"username",
"address.street",
"address.city",
"orders.products.name"
));
// Pretty formatted JSON with 2-space indentation
String prettyJson = dotPathQL.toJson(result, true);
//or
String prettyJson = dotPathQL.toJson(result, 4); // Custom indentation level
Output:
{
"username": "john_doe",
"address": {
"street": "123 Main St",
"city": "Springfield"
},
"orders": [
{
"products": [
{
"name": "Laptop"
},
{
"name": "Mouse"
}
]
}
]
}
// Compact single-line JSON
String compactJson = dotPathQL.toJson(result, false);
System.out.println(compactJson);
Output:
{"username": "john_doe", "address": {"street": "123 Main St", "city": "Springfield"}, "orders": [{"products": [{"name": "Laptop"}, {"name": "Mouse"}]}]}
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.
Map<String, Object> userMap = dotPathQL.toMap(userObject);
You can also easy access the map result using the DotUtils
utility methods:
List<String> paths = DotUtils.parsePaths("locations[home.street,work.city],contact[email,phone.mobile],age");
// Result: ["locations[home.street,work.city]", "contact[email,phone.mobile]", "age"]
// Step 1
DotPathQL dotPathQL = new DotPathQL();
Map<String, Object> result = dotPathQL.filter(userObject, List.of(
"address",
"friendList",
"games"
));
// Step 2: Accessing the result
Map<String, Object> address = DotUtils.mapFrom(result, "address");
List<Map<String, Object>> friendList = DotUtils.listFrom(result, "friendList");
Object[] games = DotUtils.arrayFrom(result, "games");
- Java: 17 or higher
- Build Tool: Maven
- Dependencies: None
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
This project is available under the MIT License.