Live Data 1: Your API, as a Type
Problem statement
Every model so far has generated data shaped like a statistical process. But sometimes the thing you need synthetic data for isn’t a distribution — it’s whatever shape a real system already returns: your own product API’s JSON, a partner’s webhook payload, a database column holding a serialized record written by something else entirely. Hand-flattening that shape into individual DataPoint fields and reassembling it downstream works right up until the shape changes, and then you’re maintaining two definitions of the same object.
The model
Convert (Pro) exists for exactly this purpose: it takes a single upstream string — from ServiceCall, FlatFile, or Database, anything that produces one — and deserializes it into a business object implementing ISerializable, wrapped so a downstream consumer can read it back out fully typed. There’s a worked example of this in Customization.md (§9.3).
The type — plain Java, no SpiceGrinder-specific machinery beyond AbstractSerializable (imports and the full field-validation trimmed here for space; the exact listing is in Customization.md):
public class Product extends AbstractSerializable {
private String sku = "";
private double price = 0.0;
private boolean inStock = false;
public Product() { }
@Override
protected void populate() {
add("sku", sku);
add("price", price);
add("inStock", inStock);
}
@Override
public void deserialize(Map<String, Object> fields) throws Exception {
super.deserialize(fields);
this.sku = (String) fields.get("sku");
this.price = (Double) fields.get("price");
this.inStock = (Boolean) fields.get("inStock");
}
}
The model uses a stand-in “live feed” that gets its values from a file using a FlatFile component: one JSON object per line. A Database or ServiceCall uses a similar mechanism. The value read from the file feeds into Convert, which also needs the name of your business object Java class (and that class needs to be in your CLASSPATH at runtime):
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<root node="Product"/>
<nodes>
<Convert name="Product" class="com.example.grind.Product">
<input name="RawFeed"/>
</Convert>
<FlatFile name="RawFeed" file="products.txt" delimiter="|" mode="sequential" onExhaustion="repeat"/>
</nodes>
</dataset>
Each line of the feed is exactly what your “API” would actually send:
{"sku":"ANCH-38-BOLT","price":4.25,"inStock":true}
{"sku":"TRQ-WR-12DR","price":89.99,"inStock":true}
{"sku":"HEXNUT-M8","price":0.35,"inStock":true}
Run the model and Convert hands each line back out through the same round trip:
"{""sku"":""ANCH-38-BOLT"",""price"":4.25,""inStock"":true}"
"{""sku"":""TRQ-WR-12DR"",""price"":89.99,""inStock"":true}"
"{""sku"":""HEXNUT-M8"",""price"":0.35,""inStock"":true}"
(The doubled quotes are Grind’s own CSV writer quoting a value that contains commas — standard CSV escaping, not something Convert added. The object itself is exactly {"sku":"ANCH-38-BOLT","price":4.25,"inStock":true}.)
It’s not just text that came back
That output alone doesn’t prove Convert actually deserialized anything — an untouched passthrough would look identical. What proves it is asking for the fields with their real types, not string-parsing the output back apart:
Product p = (Product) objectDataPoint.getObject();
p.getSku(); // "ANCH-38-BOLT" -- a String
p.getPrice(); // 4.25 -- a double, not a string that happens to look numeric
p.isInStock(); // true -- a boolean
Running exactly that against the model above returns sku=ANCH-38-BOLT price(double)=4.25 inStock(boolean)=true — genuine typed fields, reconstructed from raw text.
Checking this yourself requires running SpiceGrinder inside your own program. Fortunately, that’s a capability that exists, and we plan to cover it in more detail in a different lesson track.
Why Convert matters
Nothing about Product’s shape lives inside SpiceGrinder itself — change Product’s fields tomorrow and the only thing that needs to change is Product itself and whatever’s producing the feed. That’s why this feature exists: to bring an external business object into SpiceGrinder, and by extension, your model.
If you ever find Convert silently dropping a field, or a value coming back as the wrong type from what deserialize says it should be — tell us.