Evaluating Trees¶
Evaluating trees is the process of traversing a Bonsai tree based on a context to retrieve the appropriate data. This guide explains how to create contexts, evaluate trees, and process the results.
Creating a Context¶
Before evaluating a tree, you need to create a Context object that contains the data needed to evaluate conditions on Edges:
// Create a Context from a JSON string
String json = "{\"user\": {\"age\": 25, \"country\": \"US\", \"type\": \"premium\"}}";
Context context = Context.builder()
.documentContext(JsonPath.parse(json))
.build();
// Create a Context from an object
User user = new User("John", 25, "US", "premium");
Context context = Context.builder()
.documentContext(JsonPath.parse(user))
.build();
// Create a Context with preferences
Map<String, Knot> preferences = Map.of("homePageConfig", customKnot);
Context context = Context.builder()
.documentContext(JsonPath.parse(user))
.preferences(preferences)
.build();
// Create a Context with metadata
Context context = Context.builder()
.documentContext(JsonPath.parse(user))
.metadata(Map.of(
"requestId", UUID.randomUUID().toString(),
"timestamp", System.currentTimeMillis(),
"source", "mobile-app"
))
.build();
Basic Evaluation¶
To evaluate a tree, you call the evaluate
method with a key and a context:
// Evaluate the tree
KeyNode result = bonsai.evaluate("userEligibility", context);
// Access the evaluation result
Boolean isEligible = result.getValue().getBooleanValue();
System.out.println("User is eligible: " + isEligible); // true
Accessing Evaluation Results¶
The result of an evaluation is a KeyNode
object that reflects the structure of the KnotData in the final Knot reached during traversal:
Value Results¶
For ValuedKnotData, you can access the primitive value using NodeUtils helper methods:
// String value
String stringValue = NodeUtils.asString(result.getValue(), "defaultValue");
// Boolean value
Boolean booleanValue = NodeUtils.asBoolean(result.getValue(), false);
// Number value
Double numberValue = NodeUtils.asNumber(result.getValue(), 1);
// JSON value
JsonNode jsonValue = NodeUtils.asJsonNode(result.getValue(), JsonNodeFactory.instance.objectNode());
List Results¶
For MultiKnotData, you can access the list of KeyNodes:
// Get the list of KeyNodes
List<KeyNode> items = result.getKeyNodeList();
// Process each item
for (KeyNode item : items) {
// Access the item's value
String value = NodeUtils.asString(item.getValue(), "");
System.out.println("Item value: " + value);
}
Map Results¶
For MapKnotData, you can access the map of string keys to KeyNodes:
// Get the map of KeyNodes
Map<String, KeyNode> properties = result.getKeyNodeMap();
// Access specific properties
String name = NodeUtils.asString(properties.get("name").getValue(), "");
Double age = NodeUtils.asString(properties.get("age").getValue(), "");
Boolean active = NodeUtils.asString(properties.get("active").getValue(), "");
System.out.println("User: " + name + ", Age: " + age + ", Active: " + active);
Flat Evaluation¶
For debugging or visualization purposes, you can get a flat representation of the evaluated tree:
// Get a flat representation of the evaluated tree
FlatTreeRepresentation flatTree = bonsai.evaluateFlat("userEligibility", context);
// Access the flat tree information
String rootKnotId = flatTree.getRootKnotId();
Map<String, Knot> knots = flatTree.getKnots();
Map<String, Edge> edges = flatTree.getEdges();
List<String> traversedEdgeIds = flatTree.getTraversedEdgeIds();
Contextual Preferences¶
The Context can include a preferences
map, which maps keys to specific Knots. When evaluating a key, if the key is found in the preferences map, Bonsai will return the associated Knot directly, bypassing the normal tree traversal:
// Get a custom knot for a specific user
Knot customHomePageKnot = bonsai.getKnot("custom-home-page");
// Create a context with preferences
Map<String, Knot> preferences = Map.of("homePage", customHomePageKnot);
Context context = Context.builder()
.documentContext(JsonPath.parse(userData))
.preferences(preferences)
.build();
// Evaluate the tree - will return the custom knot directly
KeyNode result = bonsai.evaluate("homePage", context);
This is useful for scenarios like user-specific overrides of default configurations.
Custom Context Implementation¶
You can create custom Context implementations for specific application needs:
public class UserContext extends Context {
private User user;
public UserContext(User user) {
super(JsonPath.parse(user));
this.user = user;
}
// Custom methods to access user data
public int getUserAge() {
return user.getAge();
}
public String getUserCountry() {
return user.getCountry();
}
}
// Create a custom context
User user = new User("John", 25, "US", "premium");
UserContext context = new UserContext(user);
// Evaluate the tree with the custom context
KeyNode result = bonsai.evaluate("userEligibility", context);
Evaluation Process¶
Understanding the evaluation process can help you design effective tree structures:
- Bonsai starts at the root Knot for the specified key
- For each Knot, it evaluates the Edges in order
- For each Edge, it evaluates all filters against the Context
- If all filters on an Edge evaluate to true, it follows that Edge to the target Knot
- If no Edge's filters match, the traversal stops at the current Knot
- The process continues until it reaches a Knot with no matching Edges
- The final Knot's data is returned as the result
Error Handling¶
Evaluation can throw various exceptions:
try {
KeyNode result = bonsai.evaluate("nonExistentKey", context);
} catch (BonsaiError e) {
if (e.getErrorCode() == BonsaiErrorCode.KEY_NOT_FOUND) {
// Handle key not found error
System.err.println("Key not found: " + e.getMessage());
} else {
// Handle other errors
throw e;
}
}
Performance Considerations¶
- Context Size: Large Context objects can impact performance
- Tree Depth: Deep trees require more traversal steps
- Filter Complexity: Complex JsonPath expressions can be expensive to evaluate
- Caching: Consider caching evaluation results for frequently used keys and contexts
Best Practices¶
- Structure your Context data in a way that makes JsonPath expressions simple and intuitive
- Use consistent data structures across similar contexts for easier maintenance
- Document the expected Context structure for each key to ensure proper usage
- Consider performance implications of large Context objects
- Handle evaluation errors appropriately in your application
- Use preferences judiciously to avoid bypassing the rule engine unnecessarily