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
{{ message }}
Repository navigation
Commit a0247c4
Browse filesBrowse the repository at this point in the historyBrowse files
fix(codegen-kotlin): parse jsonb open-bag to JsonElement at persistence+CRUD, not just payload (#98)
PR #104 exposed a `field.string @dbColumnType=jsonb` open bag as a parsed kotlinx
`JsonElement` only in the REST *payload* surface; the derived entity-CRUD controller
reuses the Exposed entity data class as its DTO, and that property was still `String`
(the Exposed column used an identity codec `jsonb(col, { it }, { it })` keeping raw
JSON text). So the entity-CRUD contract still exposed a jsonb bag as a double-encoded
`String`, unlike TS (`z.unknown()`), Python (`Any`), Java, and C# (`JsonDocument`).
Per the maintainer decision on #98 — uniform parsed value at the API boundary, all
ports — this does the Kotlin analogue of the C# `jsonb <-> JsonDocument` change:
- `KotlinTypeMapper.kotlinTypeName` now maps the jsonb open bag to `JsonElement`
(keyed on the existing `isJsonbOpenBag` predicate from #104), so the entity data
class — and thus the reused entity-CRUD DTO — is a parsed JSON value.
- `KotlinTypeMapper.exposedColumnSpec` changes the jsonb codec from the identity
passthrough to a real `JsonElement` codec: encode `{ it.toString() }` (a
JsonElement's toString is canonical JSON), decode `{ Json.parseToJsonElement(it) }`
(concrete return anchors the column generic to `Column<JsonElement>`). It is STILL
a real `jsonb(...)` column — only the codec/type changes, not that it is jsonb.
- `KotlinExposedTableGenerator` emits the `kotlinx.serialization.json.Json` import
for tables carrying a jsonb open bag (direct or flattened sub-field), without
double-emitting the `jsonb` extension import.
Net: column / entity data class (CRUD DTO) / payload all expose `JsonElement` for a
jsonb bag — fully uniform. A plain `field.string` stays `String`; `@dbColumnType:uuid`
stays `String`. The wire/serialized form is byte-identical to the other ports (`{...}`);
only the in-process Kotlin type changes String -> JsonElement.
Tests (TDD, RED->GREEN):
- KotlinTypeMapperTest: the jsonb case now asserts `kotlinTypeName == JsonElement` and
the new column codec, while still asserting the column is a real `jsonb(...)`.
- KotlinEntityGeneratorTest: new test proving the entity data class types the jsonb
field as `JsonElement` (the entity-CRUD path).
- integration-tests-kotlin: the hand-written reference `AssetTable.payload` and the
`QueryScenarioRunner` read/write coercion + `RuntimeReturnTypeTest` updated to the
parsed `JsonElement` read-back (wire form unchanged). Full module green (90/90)
against Testcontainers Postgres.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LuZWKnWzYGVnESijL7uuky
Copy file name to clipboardExpand all lines: server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/KotlinExposedTableGenerator.kt
+20-1Lines changed: 20 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -210,7 +210,20 @@ open class KotlinExposedTableGenerator : MultiFileDirectGeneratorBase<MetaObject
210
210
val uuidGeneratedPk = primary?.isUuid ==true&&!isView && singlePrimaryFieldName !=null
211
211
212
212
val objectColumns = buildObjectColumns(entity, primaryFieldSet, loader)
213
-
val needsJsonbImport = objectColumns.any { it.kind ==ObjectColumnKind.JSONB }
213
+
// A `field.string @dbColumnType=jsonb` open bag now decodes to a kotlinx `JsonElement`
214
+
// (issue #98) via `{ Json.parseToJsonElement(it) }`, so its table file needs the
215
+
// `kotlinx.serialization.json.Json` import too (same import block as the object/map jsonb
216
+
// columns below). Detect it on direct fields AND flattened object sub-fields — the same two
217
+
// surfaces that contribute columns / imports. Folding it into `needsJsonbImport` (rather
218
+
// than `columnFunctionImports`) keeps the `jsonb` extension import emitted exactly once.
219
+
val hasStringJsonbOpenBag = entity.metaFields.any { KotlinTypeMapper.isJsonbOpenBag(it) } ||
220
+
entity.metaFields.any { f ->
221
+
f isObjectField&& readStorage(f) ==STORAGE_FLATTENED&&
Copy file name to clipboardExpand all lines: server/java/codegen-kotlin/src/main/kotlin/com/metaobjects/generator/kotlin/apidocs/KotlinApiModelBuilder.kt
+5-5Lines changed: 5 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -389,11 +389,11 @@ class KotlinApiModelBuilder {
389
389
/**
390
390
* The simple Kotlin type label for a documented field (enum → generated enum class name).
391
391
*
392
-
* [forPayload] selects the payload type path ([KotlinTypeMapper.payloadTypeName]) so a
393
-
* `field.string @dbColumnType=jsonb` open bag is documented as the parsed JSON value
394
-
* (`JsonElement`) the payload data class actually emits (issue #98); the entity model field
395
-
* (forPayload = false) stays `String`, matching its data-class property. Keeps the documented
396
-
* type == the generated type on both surfaces.
392
+
* [forPayload] selects the payload type path ([KotlinTypeMapper.payloadTypeName]); the entity
393
+
* model field path uses [KotlinTypeMapper.kotlinTypeName]. As of the #98 uniform-parsed-value
394
+
* cutover BOTH surfaces document a `field.string @dbColumnType=jsonb` open bag as the parsed
395
+
* JSON value (`JsonElement`) — the payload dataclass and the entity data class (the reused
396
+
* entity-CRUD DTO) now agree. Keeps the documented type == the generated type on both surfaces.
0 commit comments