Mixed-Format Projects: XML Envelope with an Embedded JSON Payload¶
A common integration shape: an XML document (XSD-described) where one element's
text content is a JSON document (JSON Schema-described). schema2class handles
both halves in one Gradle invocation — no IR-level linkage needed, and none is
performed: the JSON-bearing element stays a String in the envelope class, and
the payload class is generated from its own schema.
Build configuration¶
plugins {
kotlin("jvm")
id("ca.esmcelroy.schema2class")
}
schema2class {
schemas {
create("envelope") {
source = file("schemas/envelope.xsd")
annotationMode = "XMLUTIL" // XML round-trip via pdvrieze/xmlutil
// packageName omitted → derived from targetNamespace
// xs:import / xs:include resolved transitively
}
create("payload") {
source = file("schemas/payload.schema.json")
packageName = "com.corp.payload" // required for JSON Schema
annotationMode = "KOTLINX_SERIALIZATION"
}
}
}
schema2classGenerate runs before compileKotlin automatically (the generated
directory is wired into the main source set when the Kotlin JVM plugin is present).
For Jackson output, set omitNulls = true on a schema to emit
@JsonInclude(JsonInclude.Include.NON_NULL). For kotlinx output, keep nullable
defaults and configure the runtime encoder with Json { encodeDefaults = false }.
For integer-valued enums in Jackson mode, set enumUnknownFallback = true to emit
numeric @JsonValue serialization plus a @JsonCreator factory that maps
unrecognized wire values to UNKNOWN.
Using the generated classes together¶
Suppose the envelope XSD declares <xs:element name="payload" type="xs:string"/>
and the JSON Schema describes TelemetryPayload:
val payload = TelemetryPayload(deviceId = "dev-42", recordedAt = now, readings = readings)
// Embed: serialize the payload to JSON text, place it in the envelope field
val envelope = Envelope(
messageId = "m-1",
payload = Json.encodeToString(payload),
)
// Send: the envelope round-trips as XML
val xmlText = XML.encodeToString(envelope)
// Receive: decode the envelope, then decode the embedded JSON
val received = XML.decodeFromString<Envelope>(xmlText)
val receivedPayload = Json.decodeFromString<TelemetryPayload>(received.payload)
Per-schema configuration reference¶
| Property | XSD | JSON Schema |
|---|---|---|
source |
required | required |
packageName |
optional — omitted: derive from namespace(s) + resolve imports; set: single-document parse into that package | required |
packageOverrides |
namespace URI → package map for multi-file resolution | ignored |
wireNamespace |
optional XML wire namespace emitted in xmlutil annotations | ignored |
wireNamespaceOverrides |
schema namespace URI → XML wire namespace map for multi-file XSD resolution | ignored |
nameBindings |
ignored today | optional sidecar file for friendly generated names; see docs/naming-bindings.md |
annotationMode |
NONE / KOTLINX_SERIALIZATION / XMLUTIL / JACKSON |
same (XMLUTIL adds no value for JSON) |
omitNulls |
in JACKSON mode emits class-level @JsonInclude(JsonInclude.Include.NON_NULL) |
same; in kotlinx modes, pair nullable defaults with Json { encodeDefaults = false } |
enforceConstraints |
emits require guards for supported ranges, lengths, patterns, and list cardinality |
same |
enumUnknownFallback |
in JACKSON mode, integer enum restrictions emit numeric wire values and opt-in UNKNOWN fallback |
same for integer-valued JSON Schema enums; kotlinx numeric unknown fallback is not generated yet |
The equivalent CLI flag is --enum-unknown-fallback, used with
--annotation-mode jackson.