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)));
As in protobuf, an unset field is not written and there is no way to say "explicitly null" on the wire: 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.