Skip to content

Uniform API to read/write tree data models - cross-format, cross-library.

License

Notifications You must be signed in to change notification settings

filip26/tree-io

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🌳 Tree I/O

Uniform API to read/write tree data models β€” cross-format, cross-library.

tree-io provides a consistent abstraction for working with tree-like data structures. It is format-agnostic (JSON, YAML, CBOR, …) and library-agnostic (Jackson, Gson, Jakarta, …), allowing you to read, manipulate, and write trees uniformly without depending on a specific parser or serializer.

✨ Features

  • 🌐 Uniform, library-agnostic API for tree data processing
  • πŸ—‚οΈ Supports multiple formats: JSON, YAML, CBOR
  • πŸ”Œ Works with Jackson, Gson, Jakarta, and other libraries
  • πŸ› οΈ Extensible adapter model for adding new formats or libraries

πŸ—οΈ Use Cases

  • Uniform processing of tree-structured data
  • Building library-agnostic processors and pipelines
  • Manipulating hierarchical data in a consistent way

πŸ”Œ Implementations of Tree I/O API

Artifact Version Javadoc
Tree I/O API Maven Central javadoc
Jakarta JSON API Maven Central javadoc
Jackson 2 Tree Model Maven Central javadoc
CBOR Maven Central javadoc

Examples

High-Level Transformation

The most common use case is a full transformation from a source to a destination.

// Have a source object (e.g., a Map) and a destination (e.g., a JsonGenerator)
Map<String, Object> source = Map.of("hello", "world");
JsonGenerator destination = ... ;

// Create an adapter for the source and a writer for the destination
var adapter = new NativeAdapter();
var jsonWriter = new Jackson2Writer(destination);
var cborWriter = new CborWriter(destination);

// Run the transformation with a single call
jsonWriter.node(source, adapter);
// or/and
cborWriter.node(source, adapter);

Direct Node Inspection using NodeAdapter

Use a NodeAdapter directly when you need to read, inspect, or extract specific values from a tree structure without traversing the entire tree.

// Given any 'node' object and a suitable 'adapter'...

// To inspect and process it safely:

// 1. Check for structural types first.
if (adapter.isMap(node)) {
    // If it's a map, iterate its entries.
    for (Entry<?, ?> entry : adapter.entries(node)) {
        // ... process key and value recursively
    }

} else if (adapter.isCollection(node)) {
    // If it's a collection, iterate its elements.
    for (Object element : adapter.elements(node)) {
        // ... process element recursively
    }

// 2. Then, check for scalar types.
} else if (adapter.isString(node)) {
    String value = adapter.stringValue(node);

} else if (adapter.isNumber(node)) {
    // For numbers, check for the specific type before extracting.
    if (adapter.isIntegral(node)) {
        long intValue = adapter.longValue(node);
    } else {
        BigDecimal decimalValue = adapter.decimalValue(node);
    }

} else if (adapter.isNull(node)) {
    // It's a null value.
}

Manual Traversal using NodeVisitor

For complex processing like searching or validation, you can manually iterate through the tree using the step() method of a NodeVisitor.

// Given a source object and a suitable adapter...
Object source = ... ;
NodeAdapter adapter = ... ;

// 1. Create the visitor instance.
NodeVisitor visitor = NodeVisitor.of(source, adapter);

// 2. Loop step-by-step through every node in the tree.
while (visitor.step()) {

    // 3. Inspect the visitor's state after each step.
    Object node = visitor.currentNode();
    NodeType type = visitor.currentNodeType();
    Context context = visitor.currentNodeContext();

    // 4. Perform actions based on the node's role in the tree.
    switch (context) {

        case ROOT:
            // Process the top-level node.
            break;

        case PROPERTY_KEY:
            // The current node is a key in a map.
            // The next step() will move to its value.
            break;

        case PROPERTY_VALUE:
            // The current node is a value in a map.
            break;

        case COLLECTION_ELEMENT:
            // The current node is an element in a collection.
            break;

        case END:
            // A synthetic marker showing a map or collection has ended.
            break;
    }
}

πŸ“¦ Artifacts

<!-- Core API: uniform interfaces implemented by all adapters -->
<dependency>
    <groupId>com.apicatalog</groupId>
    <artifactId>tree-io-api</artifactId>
    <version>${tree-io.version}</version>
</dependency>

<!-- Available adapters: include the one(s) matching your use case -->

<!-- Jakarta JSON API adapter -->
<dependency>
    <groupId>com.apicatalog</groupId>
    <artifactId>tree-io-jakarta</artifactId>
    <version>${tree-io.version}</version>
</dependency>

<!-- Jackson 2 Tree Model adapter -->
<dependency>
    <groupId>com.apicatalog</groupId>
    <artifactId>tree-io-jackson2</artifactId>
    <version>${tree-io.version}</version>
</dependency>

<!-- CBOR adapter -->
<dependency>
    <groupId>com.apicatalog</groupId>
    <artifactId>tree-io-cbor</artifactId>
    <version>${tree-io.version}</version>
</dependency>

🀝 Contributing

Contributions are welcome! Please submit a pull request.

Building

Fork and clone the repository, then build with Maven:

> cd tree-io
> mvn package

πŸ’Ό Commercial Support

Commercial support and consulting are available.
For inquiries, please contact: filip26@gmail.com

About

Uniform API to read/write tree data models - cross-format, cross-library.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •