# Reading values

This page covers the read side of the Tree API: getting values back out of a document, with automatic type conversion, null-safe defaults, iteration and type inspection. For building a document see Data Manipulation; for working with whole collections and nested shapes see Maps, lists, sets & nested structures.

The getters described here never mutate the node — they read the value and (when needed) convert a copy of it.

import io.datatree.Tree;
import java.util.List;
import java.math.BigDecimal;

# Raw vs. converted values

asObject() returns the underlying Java value, untouched:

Tree node = new Tree().put("a", 42);
Object raw = node.get("a").asObject();
// raw == Integer 42

The typed getters return the value converted to the requested type. Conversion is automatic, so a node that holds the String "2" still reads back as the number 2:

Tree node = new Tree().put("count", "2");
int n = node.get("count").asInteger();
// n + 3 == 5

There is a getter for every scalar type — asByte asShort asInteger asLong asFloat asDouble asBoolean asString asBytes asDate asUUID asBigDecimal asBigInteger asInetAddress. Each returns the boxed value (or null if the node's value is null or cannot be converted):

Tree node = new Tree();
node.put("name", "John");
node.put("age", 30);
node.put("balance", "1234.56");

String     name    = node.get("name").asString();        // "John"
Integer    age     = node.get("age").asInteger();         // 30
BigDecimal balance = node.get("balance").asBigDecimal();  // 1234.56

# Reading by path

get(String path) returns the child Tree at a path, or null if the path does not exist:

Tree node = new Tree().put("a", 1);
node.get("a");         // a Tree wrapping 1
node.get("missing");   // null

isExists(String path) tells you whether a path is present:

node.isExists("a");        // true
node.isExists("missing");  // false

Paths are JavaScript-like: a dot steps into an object, and [index] steps into an array. Mixing the two in a single expression is how you reach deep into a hierarchical document (these are the "JSON path" functions). Take an order with a nested customer object and an items array of objects:

Tree order = new Tree();
order.put("customer.name", "Alice");
order.put("customer.city", "Phoenix");
Tree items = order.putList("items");
items.addMap().put("sku", "A-1").put("qty", 2);
items.addMap().put("sku", "B-7").put("qty", 5);
{
  "customer":{
    "name":"Alice",
    "city":"Phoenix"
  },
  "items":[
    {
      "sku":"A-1",
      "qty":2
    },
    {
      "sku":"B-7",
      "qty":5
    }
  ]
}

Any value is then one path expression away — combine object keys and array indices freely:

order.get("customer.name").asString();   // "Alice"  — object navigation
order.get("items[0].sku").asString();    // "A-1"    — array index + field
order.get("items[1].qty").asInteger();   // 5
order.get("items[0].qty", 0);            // 2        — null-safe read by path

# Null-safe defaults

The get(path, defaultValue) family reads and converts in one call, returning the default when the path is missing or the value cannot be converted. This is the null-safe way to read — no intermediate null check, no NullPointerException. There is an overload for each scalar type (int double byte float long boolean byte[] short String UUID Date BigDecimal BigInteger InetAddress):

Tree node = new Tree().put("age", 30);

int    age     = node.get("age", 0);          // 30
int    weight  = node.get("weight", -1);      // -1  (path missing)
String nick    = node.get("nickname", "n/a"); // "n/a"

getObject(path, defaultValue) is the generic version. It converts to the default value's type and falls back to the default when the path is missing:

Tree node = new Tree().put("price", "19.99");
BigDecimal price = node.getObject("price", BigDecimal.ZERO);
// 19.99

# Reading collections

asList(Class<T>) copies all children of a node into a List, converting each element to T:

Tree node = new Tree();
node.putList("ids").add("1").add("2").add("3");

List<Integer> ids = node.get("ids").asList(Integer.class);
// [1, 2, 3]

A Tree is Iterable<Tree>, so a for loop walks the elements of a list/set or the values of a map. For a list, iterate the children directly:

Tree node = new Tree();
node.putList("ids").add(101).add(102).add(103);

for (Tree child : node.get("ids")) {
    System.out.print(child.asInteger() + " ");
}
// 101 102 103

For a map, each iterated child carries its key — read it with getName():

Tree node = new Tree().put("firstName", "John").put("lastName", "Doe");

for (Tree child : node) {
    System.out.println(child.getName() + "=" + child.asString());
}
// firstName=John
// lastName=Doe

stream() returns a Stream<Tree> for filter/map/collect pipelines:

Tree node = new Tree();
node.putList("nums").add(1).add(2).add(3).add(4).add(5).add(6);

int evenSum = node.get("nums").stream()
        .filter(c -> c.asInteger() % 2 == 0)
        .mapToInt(Tree::asInteger)
        .sum();
// 12

Beyond path access, you can move around by position:

Tree node = new Tree();
node.putList("items").add("a").add("b").add("c");
Tree items = node.get("items");

items.size();                 // 3
items.isEmpty();              // false
items.getFirstChild();        // "a"
items.getLastChild();         // "c"
items.get(1);                 // "b"  (by index)

getNextSibling() / getPreviousSibling() move between the children of a map:

Tree node = new Tree().put("a", 1).put("b", 2).put("c", 3);
Tree b = node.get("b");

b.getPreviousSibling().getName();   // "a"
b.getNextSibling().getName();       // "c"

# Finding a child

find(Predicate<Tree>) walks the children and returns the first one that matches the predicate, or null if none do — the read-side counterpart of remove(Predicate):

Tree root  = new Tree();
Tree users = root.putList("users");
users.addMap().put("name", "Alice").put("role", "user");
users.addMap().put("name", "Bob").put("role", "admin");

Tree admin   = root.get("users").find(user -> "admin".equals(user.get("role", "")));
String name  = admin == null ? "n/a" : admin.get("name").asString();   // "Bob"

# Inspecting the type

getType() returns the value's class; the is… predicates answer specific questions without casting:

Tree node = new Tree();
node.put("name", "John");
node.putList("tags").add("x");
node.putMap("address").put("city", "Phoenix");

node.get("name").isPrimitive();   // true
node.get("tags").isList();        // true
node.get("address").isMap();      // true

The full set is isNull, isPrimitive, isStructure, isMap, isList, isSet, isArray, isEnumeration (List/Set/array) and isEmpty.

Next: Maps, lists, sets & nested structures puts the write and read sides together on realistic message shapes.