Data2Java is a data-to-Java deserializer that maps data into validated null-safe Java objects.
This repository is a monorepo with one core module and format-specific modules. You can depend on exactly the formats you need.
- modules/deserializer: core object-mapping/validation engine
- modules/lua2java: Lua to Java deserializer
- modules/groovy2java: Groovy to Java deserializer
- modules/toml2java: TOML to Java deserializer
- modules/json2java: JSON to Java deserializer
- modules/xml2java: XML to Java deserializer
- errors.md: shared error variants and troubleshooting
Each language module depends on deserializer only.
If you depend only on lua2java, you do not pull groovy2java, toml2java, json2java, or xml2java.
Example (Gradle):
dependencies {
implementation "org.msuo:lua2java:<version>"
}Pick one or multiple modules the same way.
Use mutable classes with fields that can be set reflectively. Field visibility can be public, protected, package-private, or private.
- Root type and nested object types should have a no-arg constructor.
- Fields should be non-primitive boxed/object types (
Integer, notint). - Value objects can validate with a single-arg constructor.
Optional<T>,List<T>,Set<T>, andMap<K,V>are supported.Class<T>is supported: provide a class name string and it resolves if assignable toT.- Nested generic combinations are supported (for example
GenericBox<List<GenericItem<String>>>,Map<StringConstructedGenericKey<Integer>, List<String>>). - Enums are parsed from string names.
- Expose values however you prefer (public fields or getters on private fields).
import java.util.*;
public class AppData {
private String name;
private Integer port = 8080; // default kept when key is missing
private Optional<String> user = Optional.empty();
private Mode mode = Mode.DEV;
private Db db = new Db();
private List<Tag> tags = new ArrayList<>();
private Map<String, PositiveInt> limits = new LinkedHashMap<>();
public String getName() { return name; }
public Integer getPort() { return port; }
public Optional<String> getUser() { return user; }
public Mode getMode() { return mode; }
public Db getDb() { return db; }
public List<Tag> getTags() { return tags; }
public Map<String, PositiveInt> getLimits() { return limits; }
public enum Mode { DEV, PROD }
public static class Db {
public String host = "localhost";
public Optional<String> password = Optional.empty();
}
public static class Tag {
public String value;
}
public static class PositiveInt {
public final Integer value;
public PositiveInt(Integer value) {
if (value == null || value <= 0) throw new IllegalArgumentException("must be > 0");
this.value = value;
}
}
}import org.msuo.data2java.*;
AppData luaData = new LuaDeserializer().deserialize(
"return { name = 'svc', port = 9090, mode = 'PROD' }",
AppData.class
);
AppData groovyData = new GroovyDeserializer().deserialize(
"return [name: 'svc', port: 9090, mode: 'PROD']",
AppData.class
);
AppData tomlData = new TomlDeserializer().deserialize(
"name = 'svc'\nport = 9090\nmode = 'PROD'",
AppData.class
);
AppData jsonData = new JsonDeserializer().deserialize(
"{\"name\":\"svc\",\"port\":9090,\"mode\":\"PROD\"}",
AppData.class
);
AppData xmlData = new XmlDeserializer().deserialize(
"<data><name>svc</name><port>9090</port><mode>PROD</mode></data>",
AppData.class
);import java.util.List;
import java.util.Map;
class GenericBox<T> { public T value; }
class GenericItem<T> { public T payload; }
class StringConstructedGenericKey<T> {
public final String value;
public StringConstructedGenericKey(String value) { this.value = value; }
}
class GenericData {
public GenericBox<List<GenericItem<String>>> foo;
public Map<StringConstructedGenericKey<Integer>, List<String>> values;
}
GenericData data = new JsonDeserializer().deserialize(
"{\"foo\":{\"value\":[{\"payload\":\"a\"}]},\"values\":{\"k1\":[\"x\",\"y\"]}}",
GenericData.class
);interface Service {}
class ServiceImpl implements Service {}
class ServiceData {
public Class<Service> impl;
}
ServiceData data = new JsonDeserializer().deserialize(
"{\"impl\":\"" + ServiceImpl.class.getName() + "\"}",
ServiceData.class
);
assertEquals(ServiceImpl.class, data.impl);Any class assignable to T is accepted for Class<T>.
Object mapping and validation are done in one pass. Unknown input fields (including nested ones) are treated as mapping errors and are included in the same aggregated exception. Field errors are collected, then one DataDeserializationException is thrown with all errors.
The exception message also includes a tree view of failing paths.
try {
new JsonDeserializer().deserialize("{\"port\":0}", AppData.class);
} catch (DataDeserializationException ex) {
ex.forEachError((segments, error) ->
System.out.println(segments + " -> " + error.getErrorType().getClass().getSimpleName())
);
DataDeserializationException.PathNode root = ex.getErrorPathTree();
System.out.println(ex.getMessage());
}See errors.md for all error variants.
Parse/eval failures are separate and throw DataSourceException.
try {
new TomlDeserializer().deserialize("name = ", AppData.class);
} catch (DataSourceException ex) {
System.out.println(ex.phase()); // parse or evaluate
System.out.println(ex.format()); // TOML / JSON / XML / Lua / Groovy
}Format-specific semantics are documented in each module README:
./gradlew clean testRun selected modules:
./gradlew :lua2java:test :groovy2java:test :toml2java:test :json2java:test :xml2java:test- Supported Java versions: Java 11 and above for all modules.
- LLM assistance was used during development of this codebase and its documentation.