You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|`JavaObjectCodeGenerator`| module `metaobjects-codegen-base` (`com.metaobjects.generator.direct.object.javacode`), a separate module from the Spring generators above. Flavor-selected via the `flavor` generator arg. `flavor=pojoAware` → `class <Name> extends PojoObject` (a concrete `MetaObjectAware` class with a `(MetaObject)` constructor) — its inherited `getMetaData()` back-reference is what breaks a default Jackson/Gson mapper, see "Serializing generated objects" below. `flavor=valueObject` → `class <Name> extends ValueObject` (map-backed; less hostile to a default mapper, but still not the sanctioned serialization path). Either concrete flavor also emits a `<Name>Extractor` plus a self-registering `ObjectClassBindingProvider`. For a plain default-Jackson-friendly type, use the `codegen-spring` record surface instead — never `pojoAware`. |
108
110
109
111
**Projections (read-only views).** An `object.projection` (read-only `source.rdb`
110
112
`@kind: view` child) is served read-only through OMDB at the ObjectManager layer
@@ -153,3 +155,69 @@ polymorphic + per-subtype-scoped repository seam the consumer implements against
153
155
Spring Data JPA / JDBC. Conformance-gated by `fixtures/api-contract-conformance/tph`
154
156
(HTTP wire shape) and `fixtures/persistence-conformance/tph-*` (single-table
155
157
runtime semantics).
158
+
159
+
## Serializing generated objects
160
+
161
+
Two paths hand you a `MetaObjectAware` instance: (a) `JavaObjectCodeGenerator`'s
162
+
flavored codegen above (a `pojoAware` or `valueObject` class), and (b) the om/omdb
163
+
runtime (`ObjectManager.getObjects(...)` / `MetaObject.newInstance()` — see the
164
+
runtime-ui reference). **A default Jackson/Gson mapper over a `PojoObject` subtype
165
+
fails on the `MetaObject` back-reference** — the inherited `getMetaData()` getter
166
+
leads a bean-style mapper into the metadata graph, and on a modular JVM into
167
+
`InaccessibleObjectException`. This is expected, not a bug to work around. If you
168
+
want a type that serializes cleanly with a bare default mapper, use the
169
+
`codegen-spring` record surface (`SpringDtoGenerator` / `SpringPayloadGenerator` /
170
+
`SpringValueObjectGenerator`) instead — never `pojoAware`.
171
+
172
+
Serialize any `MetaObjectAware` instance through the MetaObjects JSON layer's
173
+
`JsonObjectWriter`/`JsonObjectReader`, not a bare mapper — it applies the temporal
174
+
wire form below, and read/write round-trip through the same pair of calls:
Copy file name to clipboardExpand all lines: agent-context/templates/always-on.md.mustache
+1Lines changed: 1 addition & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -15,6 +15,7 @@ spine; generated code is the disposable artifact. Regenerate with `{{codegenComm
15
15
- Never hand-edit generated files — change the metadata and regenerate (three-way merge preserves hand-written regions).
16
16
- Use the generated constants for any string that names metadata.
17
17
- The loaded metadata model is READ-ONLY — never inject nodes or mutate the tree at load time (no "enrich the model on load" hooks). Need an extra field/column? Author it in the metadata, or derive it during codegen (read the metadata, emit output). Mutating the loaded model makes it diverge from what's declared — a bad practice reserved for very rare cases.
18
+
- **JVM:** serialize a MetaObject-backed instance (a `pojoAware`-flavor generated class, a runtime `ValueObject`, or any `MetaObjectAware` type) through the MetaObjects JSON layer — never hand-configure a Jackson/Gson mapper around the framework fields to make a default mapper cope.
18
19
19
20
## Authoring rules you must not violate
20
21
- Nodes are fused-key maps: `{"<type>.<subType>": { ... }}` (e.g. `{"field.string": {"name": "email"}}`) — never split the type and subtype into separate keys.
Copy file name to clipboardExpand all lines: docs/ports/java.md
+74-6Lines changed: 74 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -172,10 +172,13 @@ auto-create path was removed per ADR-0015.
172
172
OMDB reads the same metadata at runtime and drives CRUD; no per-entity ORM
173
173
boilerplate.
174
174
175
-
The Java port generates **no typed entity POJO** — the only entity-shaped Java
176
-
output is the immutable `<Entity>Dto` record (from `codegen-spring`). OMDB drives
177
-
CRUD against the loaded metadata plus generic `ValueObject` instances, and its API
178
-
is connection-first (you pass an `ObjectConnection` to each call):
175
+
`codegen-spring`'s only entity-shaped output is the immutable `<Entity>Dto`
176
+
record — it generates no typed entity POJO. (A typed `MetaObjectAware` class
177
+
is available separately, from `JavaObjectCodeGenerator`'s flavored codegen —
178
+
see [Serializing generated objects](#serializing-generated-objects) below.)
179
+
OMDB drives CRUD against the loaded metadata plus generic `ValueObject`
180
+
instances, and its API is connection-first (you pass an `ObjectConnection` to
181
+
each call):
179
182
180
183
```java
181
184
importcom.metaobjects.loader.MetaDataLoader;
@@ -264,13 +267,78 @@ into a Maven test (e.g. a JUnit assertion in the `test` phase).
264
267
|`SpringControllerGenerator`|`metaobjects-codegen-spring`| One `<Entity>Controller.java` per writable entity (`source.rdb @kind="table"`). Spring Boot 3.x / Spring Web MVC. Five CRUD endpoints (GET list / GET by id / POST / PATCH + PUT / DELETE) matching the cross-port [REST API contract](../features/api-contract.md). `?sort`, `?limit/?offset`, `?withCount=1` envelope, 404 + 400 envelopes per the contract. Filter operators (`eq/ne/gt/gte/lt/lte/in/like/isNull`) ship via the generated `<Entity>FilterAllowlist` (`SpringFilterAllowlistGenerator`) + the runtime `FilterParser`, wired directly into the list handler. |
265
268
|`SpringDtoGenerator`|`metaobjects-codegen-spring`| One `<Entity>Dto.java` per entity as a Java 21 `record`. Wrapped-primitive components (`Long`, `Integer`, `Boolean`) so missing JSON properties deserialise to `null`. Currency = `Long` (integer minor units cross-port invariant). Used as both request and response body. |
266
269
|`SpringRepositoryGenerator`|`metaobjects-codegen-spring`| One `<Entity>Repository.java` per writable entity as a hand-stubbed Java `interface` the consumer implements with their preferred persistence layer (Spring Data JPA / jOOQ / plain JDBC — all out of MetaObjects' concern). Nests the `SortClause` record the controller calls into. |
270
+
|`JavaObjectCodeGenerator`|`metaobjects-codegen-base`| Flavor-selected via the `flavor` generator arg (`com.metaobjects.generator.direct.object.javacode`). `flavor=pojoAware` emits `class <Name> extends PojoObject` — a concrete `MetaObjectAware` class whose inherited `getMetaData()` back-reference breaks a default Jackson/Gson mapper (see [Serializing generated objects](#serializing-generated-objects) below). `flavor=valueObject` emits a map-backed `class <Name> extends ValueObject` instead. Either flavor also emits a `<Name>Extractor` and a self-registering `ObjectClassBindingProvider`. For a plain default-Jackson-friendly type, use the `codegen-spring` record surface instead — never `pojoAware`. |
267
271
268
-
Wire any of them via the Maven plugin's `<generator>` entry pointing at
A default Jackson/Gson mapper pointed directly at a `pojoAware`-flavor class
313
+
fails on the `MetaObject` back-reference every generated `PojoObject` subtype
314
+
carries (the inherited `getMetaData()` getter leads a bean-style mapper into
315
+
the metadata graph, and on a modular JVM into `InaccessibleObjectException`)
316
+
— **this is expected, not a bug to work around.** If you want a type that
317
+
serializes cleanly with a bare default mapper, generate the `codegen-spring`
318
+
record surface instead (`SpringDtoGenerator` / `SpringPayloadGenerator` /
319
+
`SpringValueObjectGenerator`) — never `pojoAware`.
320
+
321
+
**Wire form** (`field.date` / `field.timestamp`) — a Java rendering of the cross-port contract in [`normalization.md`](../../fixtures/persistence-conformance/normalization.md) (the single source of truth):
322
+
323
+
| Field | Wire form | Example |
324
+
|---|---|---|
325
+
|`field.date`| calendar date of the instant at UTC — `YYYY-MM-DD`|`"2026-06-03"`|
326
+
|`field.timestamp` + `@localTime: true`| wall clock of the instant at UTC, no `Z`|`"2026-06-03T14:30:00.123"`|
327
+
|`field.timestamp` (default, tz-aware) | UTC instant, with `Z`|`"2026-06-03T14:30:00.123Z"`|
328
+
329
+
The fraction is millisecond resolution, trailing zeros stripped, and the `.`
330
+
plus fraction omitted entirely when zero (`.123`→`.123`, `.120`→`.12`,
331
+
`.100`→`.1`, `.000`→omitted). A `null` value writes JSON `null`. Readers stay
332
+
tolerant and backward-compatible: a JSON **number** is still read as **legacy
333
+
epoch milliseconds**; a JSON **string** is tried in order as an ISO instant
334
+
(the `Z` form) → a local date-time (no `Z`) → a date-only form, and the error
335
+
message names all three accepted forms if none match.
336
+
337
+
A hand-constructed `field.date` value carrying a sub-day time component
338
+
writes as the calendar date only (truncated on first write, stable
339
+
thereafter) — this matches the shipped OMDB DATE codec, which anchors DATE
340
+
columns at midnight UTC.
341
+
274
342
## Universal Angular 18 client
275
343
276
344
The browser-side Angular 18 client (`@metaobjectsdev/angular` +
Copy file name to clipboardExpand all lines: docs/superpowers/plans/2026-08-08-serializer-date-recursion-and-java-json-docs.md
+22-7Lines changed: 22 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,16 +13,31 @@ verified at the baseline SHA but must be **re-derived from code** before acting
13
13
14
14
## STATUS — update as you go (edit this file, commit the checkbox flips with the work)
15
15
16
-
-[ ] Phase 0 — setup, premise recon
17
-
-[ ] Unit A — wire-form implementation: serializer DATE branch + deserializer DATE split + streaming-reader split + `TemporalWireFormat` + gate tests (TDD)
18
-
-[ ] Unit B — Gson wiring siblings: `JsonObjectReader` registers serializers-only; initializer's add-flags are dead code (fix TOGETHER — they mask each other)
19
-
-[ ] Unit C — serializer write-side `@isArray` asymmetry (bounded; **maintainer checkpoint before widening**)
20
-
-[ ] Unit D — #273 docs (5 files; gated on Unit A being merged-or-on-the-same-branch)
21
-
-[ ] Free-text sweep (hazard discipline — member VALUES, spelling-agnostic)
22
-
-[ ] Independent review (branch + `no-mistakes` gate) → merge to `main` → local-ci green
16
+
-[x] Phase 0 — setup, premise recon
17
+
-[x] Unit A — wire-form implementation: serializer DATE branch + deserializer DATE split + streaming-reader split + `TemporalWireFormat` + gate tests (TDD) — `94a9f400`
18
+
-[x] Unit B — Gson wiring siblings: `JsonObjectReader` registers serializers-only; initializer's add-flags are dead code (fix TOGETHER — they mask each other) — `daa8d677`
19
+
-[x] Unit C — serializer write-side `@isArray` asymmetry (bounded; **maintainer checkpoint before widening**) — `026bc342`, `0ba2e030`. Stayed inside its bound (3 files, zero `MetaField`/`DataConverter` change); the escalation clause fired as designed — see "Carry-forward" below.
20
+
-[x] Unit D — #273 docs (5 files; gated on Unit A being merged-or-on-the-same-branch) — `30e8946c`, `7138e580`
21
+
-[x] Free-text sweep (hazard discipline — member VALUES, spelling-agnostic) — two passes, code side + doc side, clean
22
+
-[x] Independent review (branch) — final whole-branch review clean after one fix wave (`f8e10c39`); 25 deferred findings triaged, 1 parked
23
+
-[ ]`no-mistakes` gate → merge to `main` → local-ci green
0 commit comments