Examples
Each example starts from the same type definition and shows how a different module uses it — no extra mapping, no separate DTOs.
Shared type definition
All examples below use this single GlobType:
public class ProductType {
public static final GlobType TYPE;
public static final LongField id;
public static final StringField title;
public static final DoubleField price;
public static final BooleanField published;
static {
GlobTypeBuilder b = GlobTypeBuilderFactory.create("Product");
id = b.declareLongField("id");
title = b.declareStringField("title");
price = b.declareDoubleField("price");
published = b.declareBooleanField("published");
TYPE = b.build();
}
}
JSON — globs-gson
Serialize a Glob to JSON and deserialize it back:
import org.globsframework.json.GSonUtils;
// Create a product Glob
MutableGlob product = ProductType.TYPE.instantiate()
.set(ProductType.id, 1L)
.set(ProductType.title, "XPhone")
.set(ProductType.price, 999.0)
.set(ProductType.published, true);
// Encode to JSON
String json = GSonUtils.encode(product, false);
// → {"id":1,"title":"XPhone","price":999.0,"published":true}
// Decode from JSON (only fields present in ProductType.TYPE are read)
Glob decoded = GSonUtils.decode(json, ProductType.TYPE);
String title = decoded.get(ProductType.title); // "XPhone"
Sparse / PATCH semantics
Unset fields are not emitted — so partial updates carry only the fields that changed:
MutableGlob patch = ProductType.TYPE.instantiate()
.set(ProductType.price, 799.0);
// only "price" is set; the others are unset
String patchJson = GSonUtils.encode(patch, false);
// → {"price":799.0}
Protobuf wire format — globs-protobuf
The same Glob, on the protobuf wire — without protobuf-java messages, without
a .proto compiler. The mapping is an annotation on each field, so what goes
out is byte-for-byte what the equivalent .proto message would produce.
public class ProductProto {
public static final GlobType TYPE;
public static final LongField id;
public static final StringField title;
public static final DoubleField price;
public static final BooleanField published;
static {
GlobTypeBuilder b = GlobTypeBuilderFactory.create("Product");
// ProtobufField.create(number[, GrpcType]) — the proto field number and encoding
id = b.declareLongField("id", ProtobufField.create(1, ProtobufField.GrpcType.int64));
title = b.declareStringField("title", ProtobufField.create(2));
price = b.declareDoubleField("price", ProtobufField.create(3));
published = b.declareBooleanField("published", ProtobufField.create(4));
TYPE = b.build();
}
}
// Write
ProtobufWriter writer = ProtobufWriter.Builder.init().add(ProductProto.TYPE).build();
BinaryWriter binaryWriter = BinaryWriter.newHeapInstance(BufferAllocator.create());
writer.write(product, binaryWriter);
AllocatedBuffer out = binaryWriter.complete();
// Read
ProtobufReader reader = ProtobufReader.Builder
.init(GlobType::instantiate).add(ProductProto.TYPE).build();
Glob decoded = reader.read(ProductProto.TYPE, new SafeHeapReader(ByteBuffer.wrap(bytes)));
isSet survives a round-trip, a written null does not. For a binary
format that keeps that distinction and stays backward compatible, use
globs-bin-serialisation instead.
Database — globs-sql directory globs-db
Create a table, insert a row, and query it back:
// Create table from GlobType (no SQL string needed)
sqlConnection.createTable(ProductType.TYPE);
// Insert
sqlConnection.getCreateBuilder(ProductType.TYPE)
.set(ProductType.id, 1L)
.set(ProductType.title, "XPhone")
.set(ProductType.price, 999.0)
.set(ProductType.published, true)
.getRequest().apply();
sqlConnection.commit();
// Query with a typed constraint
List<Glob> results = sqlConnection
.getQueryBuilder(ProductType.TYPE,
Constraints.and(
Constraints.equal(ProductType.published, true),
Constraints.lessThan(ProductType.price, 1000.0)))
.selectAll()
.getQuery()
.executeAsGlobs();
for (Glob g : results) {
System.out.println(g.get(ProductType.title) + " — $" + g.get(ProductType.price));
}
HTTP API — globs-http
Declare URL parameters and query parameters as GlobTypes, register
handlers, and start the server. An OpenAPI JSON is generated automatically.
public class UrlParam {
public static GlobType TYPE;
public static LongField id;
static {
GlobTypeBuilder b = GlobTypeBuilderFactory.create("UrlParam");
id = b.declareLongField("id");
TYPE = b.build();
}
}
public class QueryParam {
public static GlobType TYPE;
public static BooleanField includeUnpublished;
static {
GlobTypeBuilder b = GlobTypeBuilderFactory.create("QueryParam");
includeUnpublished = b.declareBooleanField("includeUnpublished");
TYPE = b.build();
}
}
HttpServerRegister server = new HttpServerRegister("ProductAPI/1.0");
server.register("/products/{id}", UrlParam.TYPE)
.get(QueryParam.TYPE, (body, url, query, headers) -> {
long id = url.getNotNull(UrlParam.id);
boolean all = Boolean.TRUE.equals(query.get(QueryParam.includeUnpublished));
// fetch from DB...
Glob product = /* DB lookup */ null;
return CompletableFuture.completedFuture(product);
})
.post(ProductType.TYPE, (body, url, query, headers) -> {
// body is already a Glob of ProductType
return CompletableFuture.completedFuture(body);
});
GlobHttpApacheBuilder builder = new GlobHttpApacheBuilder(server);
builder.startAndWaitForStartup(bootstrap, 8080);
Command-line parsing — globs-commandline
Define your CLI flags as a GlobType; the parser fills them in from
argv. Annotations cover defaults (DefaultInteger, …), mandatory
options (Mandatory), array separators and positional arguments.
public class CliArgs {
public static final GlobType TYPE;
public static final StringField inputFile;
public static final StringField outputFile;
public static final BooleanField verbose;
static {
GlobTypeBuilder b = GlobTypeBuilderFactory.create("CliArgs");
inputFile = b.declareStringField("inputFile", Mandatory.UNIQUE);
outputFile = b.declareStringField("outputFile");
verbose = b.declareBooleanField("verbose");
TYPE = b.build();
}
}
public static void main(String[] args) {
Glob parsed = ParseCommandLine.parse(CliArgs.TYPE, args, true); // true: ignore unknown
String input = parsed.get(CliArgs.inputFile);
String output = parsed.get(CliArgs.outputFile);
boolean verbose = Boolean.TRUE.equals(parsed.get(CliArgs.verbose));
// proceed with input / output / verbose ...
}
Invoked as: myapp --inputFile data.csv --outputFile out.json --verbose — an
option is --<field name>, or --<name> from a
FieldName annotation, and a BooleanField is a bare flag.
Nested Globs
GlobField and GlobArrayField embed Globs inside Globs —
great for hierarchical data like an order with line items:
public class LineItemType {
public static final GlobType TYPE;
public static final StringField sku;
public static final IntegerField qty;
public static final DoubleField unitPrice;
static {
GlobTypeBuilder b = GlobTypeBuilderFactory.create("LineItem");
sku = b.declareStringField("sku");
qty = b.declareIntegerField("qty");
unitPrice = b.declareDoubleField("unitPrice");
TYPE = b.build();
}
}
public class OrderType {
public static final GlobType TYPE;
public static final StringField orderId;
public static final GlobArrayField<LineItemType> items;
static {
GlobTypeBuilder b = GlobTypeBuilderFactory.create("Order");
orderId = b.declareStringField("orderId");
items = b.declareGlobArrayField("items", () -> LineItemType.TYPE);
TYPE = b.build();
}
}
// Build nested structure
MutableGlob line1 = LineItemType.TYPE.instantiate()
.set(LineItemType.sku, "SKU-001")
.set(LineItemType.qty, 2)
.set(LineItemType.unitPrice, 49.99);
MutableGlob order = OrderType.TYPE.instantiate()
.set(OrderType.orderId, "ORD-2024-001")
.set(OrderType.items, new Glob[]{ line1 });
// Serialize the whole tree to JSON in one call
String json = GSonUtils.encode(order, false);
// → {"orderId":"ORD-2024-001","items":[{"sku":"SKU-001","qty":2,"unitPrice":49.99}]}
Reading the children back
get gives a bare Glob[], null included.
getT gives a TGlobArray<LineItemType> — the declared child
type travels with the value, and the null handling comes with it
(more on TGlob):
TGlobArray<LineItemType> items = order.getT(OrderType.items);
double total = items.stream()
.mapToDouble(i -> i.get(LineItemType.unitPrice) * i.get(LineItemType.qty))
.sum(); // 99.98
Glob first = items.at(0);
if (items.isNull()) { /* the field was never set */ }
Dynamic types from JSON schema
When the schema is not known at compile time — e.g. reading a configuration format
defined externally — build a GlobType at runtime:
// A GlobType is itself serializable: encodeGlobType writes it, decodeGlobType reads it back
String schemaJson = GSonUtils.encodeGlobType(someType);
GlobType eventType = GSonUtils.decodeGlobType(
schemaJson,
resolver, // a GlobTypeResolver, for nested / referenced types
true); // ignore what the resolver does not know
// Now use it exactly like a statically-defined type
Glob event = GSonUtils.decode(incomingJson, eventType);
Field sourceField = eventType.getField("source");
String source = (String) event.getValue(sourceField);
A type built at runtime from a database (extractType(tableName) in
globs-sql) or from any other description behaves identically — same
serializers, same queries, same visitors.