A pure Java 25 library for processing XML and JSON with YAML-driven dynamic validation.
Zero external dependencies. Hand-written parsers. Modern Java features.
- Unified Data Model — Parse XML or JSON into a single tree structure (
DataNode) - Hand-Written Parsers — Custom recursive descent parsers for XML, JSON, and YAML (no external libraries)
- Get/Set by Tag Name — Retrieve or update values anywhere in the tree by tag name
- Dot-Notation Path Access — Navigate nested structures with
person.address.citysyntax - Deep Search — Find all nodes matching a tag name or predicate across the entire tree
- Tree Manipulation — Add, remove, clone nodes programmatically
- Format Conversion — Convert XML to JSON and vice versa
- YAML-Driven Validation — Define validation rules in YAML, apply them dynamically
- Java 25 Features — Records, sealed interfaces, pattern matching, switch expressions, text blocks, SequencedCollections
import io.dataforge.DataForge;
// Parse XML (auto-detected)
var doc = DataForge.parse("<user><name>Alice</name><age>30</age></user>");
// Get value by tag name (deep search)
String name = doc.getValueByTag("name").orElse("unknown");
// Set value by tag name
doc.setValueByTag("age", "31");
// Get value by dot-notation path
String city = doc.getValueByPath("address.city").orElse("?");
// Convert to JSON
String json = doc.toJson();
// Validate with YAML rules
String yaml = """
rules:
name:
required: true
type: string
minLength: 1
age:
type: integer
min: 0
max: 150
""";
var result = doc.validate(yaml);
if (!result.isValid()) {
System.out.println(result.report());
}// Auto-detect format from content
DataForge.parse(String content)
// Explicit format
DataForge.parse(String content, DataFormat.XML)
DataForge.parse(String content, DataFormat.JSON)
// From file (format detected by extension)
DataForge.parseFile("data.xml")
DataForge.parseFile("data.json")
// Create empty document
DataForge.create("rootElementName")// By tag name (deep search — finds anywhere in tree)
doc.getValueByTag("tagName") // Optional<String> — first match
doc.getAllValuesByTag("tagName") // List<String> — all matches
// By dot-notation path
doc.getValueByPath("person.address.city") // Optional<String>
doc.getNodeByPath("person.address") // Optional<DataNode>
// Search
doc.findFirst("tagName") // Optional<DataNode>
doc.findAll("tagName") // List<DataNode>// By tag name
doc.setValueByTag("tag", "newValue") // int — updates ALL matches, returns count
doc.setFirstValueByTag("tag", "newValue") // boolean — updates first match only
// By path (creates intermediate nodes if needed)
doc.setValueByPath("a.b.c", "value")// Add children
doc.addChild("parentTag", "childName", "childValue")
doc.addChild("parentTag", dataNode)
doc.addToRoot("name", "value")
doc.addToRoot(dataNode)
// Remove
doc.removeByTag("tagName") // int — returns count removed
// Clone
DataNode clone = doc.root().deepClone()
// Node info
node.path() // "root.person.address.city"
node.depth() // int
node.isLeaf() // boolean// Serialize
doc.toXml() // Pretty-printed with declaration
doc.toJson() // Pretty-printed
doc.serialize() // Back to original format
// Custom writers
doc.toXml(XmlWriter.compact())
doc.toJson(JsonWriter.compact())
doc.toXml(XmlWriter.builder().declaration(false).indent(" ").build())
// Format conversion
doc.convert() // XML->JSON or JSON->XML
doc.convertTo(DataFormat.JSON)
// Write to file
doc.writeTo("output.xml")
doc.writeTo("output.json")Define rules in YAML:
rules:
username:
required: true
type: string
minLength: 3
maxLength: 20
pattern: ^[a-zA-Z0-9_]+$
message: "Username must be 3-20 alphanumeric characters"
age:
required: true
type: integer
min: 18
max: 120
email:
required: true
notEmpty: true
status:
type: string
enum:
- active
- inactive
- pending
address:
required: true
type: object
children:
city:
required: true
type: string
zip:
required: true
pattern: ^[0-9]{5}$Supported validation rules:
| Rule | Description | Example |
|---|---|---|
required |
Field must exist | true |
type |
Type check: string, integer, number, boolean, object, array | integer |
min |
Minimum numeric value | 0 |
max |
Maximum numeric value | 150 |
minLength |
Minimum string length | 3 |
maxLength |
Maximum string length | 100 |
pattern |
Regex pattern | ^[A-Z]+$ |
enum |
Allowed values list | [a, b, c] |
notEmpty |
Value must not be blank | true |
message |
Custom error message | "Invalid!" |
children |
Nested validation rules for child nodes | (nested mapping) |
Apply validation:
// From YAML string
ValidationResult result = doc.validate(yamlString);
// From YAML file
ValidationResult result = doc.validateWithFile("rules.yaml");
// Pre-built engine (reusable)
ValidationEngine engine = ValidationEngine.fromYaml(yamlString);
ValidationResult result = doc.validate(engine);
// Inspect results
result.isValid() // boolean
result.errorCount() // int
result.getErrors() // List<ValidationError>
result.errorsForPath("username") // errors at specific path
result.report() // human-readable reportdataforge/
src/io/dataforge/
DataForge.java # Main facade API
core/
DataNode.java # Unified tree data model
DataFormat.java # XML/JSON enum with detection
DataForgeException.java # Base exception
NodeType.java # Node type enum
parser/
XmlParser.java # Hand-written XML parser
JsonParser.java # Hand-written JSON parser
YamlParser.java # Hand-written YAML parser
writer/
XmlWriter.java # XML serializer with builder
JsonWriter.java # JSON serializer with builder
validation/
ValidationEngine.java # YAML-driven validation engine
ValidationRule.java # Rule model
ValidationResult.java # Result aggregator
ValidationError.java # Error record
test/io/dataforge/
DataForgeTest.java # 117 comprehensive tests
demo/
DataForgeDemo.java # Full feature demo
# Requires Java 25+
export JAVA_HOME=/path/to/jdk-25
export PATH=$JAVA_HOME/bin:$PATH
# Compile library
mkdir -p build
javac -d build src/io/dataforge/core/*.java \
src/io/dataforge/parser/*.java \
src/io/dataforge/writer/*.java \
src/io/dataforge/validation/*.java \
src/io/dataforge/DataForge.java
# Run tests (117 tests)
javac -cp build -d build test/io/dataforge/DataForgeTest.java
java -cp build io.dataforge.DataForgeTest
# Run demo
javac -cp build -d build demo/DataForgeDemo.java
java -cp build io.dataforge.demo.DataForgeDemo
# Package as JAR
cd build && jar cf ../dataforge.jar io/ && cd ..- Records —
ValidationErroras an immutable data carrier - Sealed types ready —
NodeTypeenum for exhaustive pattern matching - Pattern matching —
switchexpressions with arrow syntax - Switch expressions — Throughout parsers, writers, and facade
- Text blocks — Multi-line strings in tests and YAML configs
- SequencedCollections —
SequencedMapfor ordered XML attributes - Enhanced
Optional—ifPresentOrElse, fluent chains String.formatted()— Modern string formattingList.of(),toList()— Immutable collection factories
MIT