Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { matchType, nullableFromUnion } from "../../Type/TypeUtils.js";

import { KotlinRenderer } from "./KotlinRenderer.js";
import { stringEscape } from "./utils.js";
import { stringEscape, unionMemberMatchPriority } from "./utils.js";

export class KotlinJacksonRenderer extends KotlinRenderer {
private unionMemberJsonValueGuard(t: Type, _e: Sourcelike): Sourcelike {
Expand All @@ -37,6 +37,7 @@ export class KotlinJacksonRenderer extends KotlinRenderer {
// and enums in the same union
(_enumType) => "is TextNode",
(_unionType) => mustNotHappen(),
(_transformedStringType) => "is TextNode",
);
}

Expand Down Expand Up @@ -76,14 +77,57 @@ import com.fasterxml.jackson.module.kotlin.*`);
this.typeGraph.allNamedTypes(),
(c) => c instanceof ClassType && c.getProperties().size === 0,
);
if (hasUnions || this.haveEnums || hasEmptyObjects) {
const usesDateTime = this.haveTransformedStringType("date-time");
const usesDate = this.haveTransformedStringType("date");
const usesTime = this.haveTransformedStringType("time");
if (
hasUnions ||
this.haveEnums ||
hasEmptyObjects ||
usesDateTime ||
usesDate ||
usesTime
) {
this.emitGenericConverter();
}

const converters: Sourcelike[][] = [];
// if (hasEmptyObjects) {
// converters.push([["convert(JsonNode::class,"], [" { it },"], [" { writeValueAsString(it) })"]]);
// }
// We don't use jackson-datatype-jsr310's JavaTimeModule because its
// serializers don't round-trip faithfully (e.g. OffsetTime pads
// "23:20:50.52Z" to "23:20:50.520Z"); the ISO formatters do.
if (usesDateTime) {
converters.push([
["convert(OffsetDateTime::class,"],
[" { OffsetDateTime.parse(it.asText()) },"],
[
' { "\\"${java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(it)}\\"" })',
],
]);
}

if (usesDate) {
converters.push([
["convert(LocalDate::class,"],
[" { LocalDate.parse(it.asText()) },"],
[
' { "\\"${java.time.format.DateTimeFormatter.ISO_LOCAL_DATE.format(it)}\\"" })',
],
]);
}

if (usesTime) {
converters.push([
["convert(OffsetTime::class,"],
[" { OffsetTime.parse(it.asText()) },"],
[
' { "\\"${java.time.format.DateTimeFormatter.ISO_OFFSET_TIME.format(it)}\\"" })',
],
]);
}

this.forEachEnum("none", (_, name) => {
converters.push([
["convert(", name, "::class,"],
Expand Down Expand Up @@ -334,19 +378,55 @@ private fun <T> ObjectMapper.convert(k: kotlin.reflect.KClass<*>, fromJson: (Jso
" = when (jn) {",
);
this.indent(() => {
const table: Sourcelike[][] = [];
// Members whose JSON representations share a node type
// (several transformed string types, or a transformed string
// type and an enum, are all TextNode) must share a single
// guard and be tried in sequence, most specific parse first.
const groups: Array<{
guard: string;
members: Array<{ name: Name; t: Type }>;
}> = [];
this.forEachUnionMember(
u,
nonNulls,
"none",
null,
(name, t) => {
table.push([
[this.unionMemberJsonValueGuard(t, "jn")],
[" -> ", name, "(mapper.treeToValue(jn))"],
]);
const guard = this.sourcelikeToString(
this.unionMemberJsonValueGuard(t, "jn"),
);
const group = groups.find((g) => g.guard === guard);
if (group === undefined) {
groups.push({ guard, members: [{ name, t }] });
} else {
group.members.push({ name, t });
}
},
);
const table: Sourcelike[][] = [];
for (const { guard, members } of groups) {
const ordered = [...members].sort(
(a, b) =>
unionMemberMatchPriority(a.t) -
unionMemberMatchPriority(b.t),
);
let expr: Sourcelike = [
ordered[ordered.length - 1].name,
"(mapper.treeToValue(jn))",
];
for (let i = ordered.length - 2; i >= 0; i--) {
expr = [
"try { ",
ordered[i].name,
"(mapper.treeToValue(jn)) } catch (e: Exception) { ",
expr,
" }",
];
}

table.push([[guard], [" -> ", expr]]);
}

if (maybeNull !== null) {
const name = this.nameForUnionMember(u, maybeNull);
table.push([
Expand Down
117 changes: 104 additions & 13 deletions packages/quicktype-core/src/language/Kotlin/KotlinKlaxonRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
import { matchType, nullableFromUnion } from "../../Type/TypeUtils.js";

import { KotlinRenderer } from "./KotlinRenderer.js";
import { stringEscape } from "./utils.js";
import { stringEscape, unionMemberMatchPriority } from "./utils.js";

export class KotlinKlaxonRenderer extends KotlinRenderer {
private unionMemberFromJsonValue(t: Type, e: Sourcelike): Sourcelike {
Expand Down Expand Up @@ -54,6 +54,21 @@ export class KotlinKlaxonRenderer extends KotlinRenderer {
".fromValue(it) }",
],
(_unionType) => mustNotHappen(),
(transformedStringType) => {
if (transformedStringType.kind === "date-time") {
return [e, ".string?.let { OffsetDateTime.parse(it) }"];
}

if (transformedStringType.kind === "date") {
return [e, ".string?.let { LocalDate.parse(it) }"];
}

if (transformedStringType.kind === "time") {
return [e, ".string?.let { OffsetTime.parse(it) }"];
}

return [e, ".string"];
},
);
}

Expand All @@ -75,6 +90,7 @@ export class KotlinKlaxonRenderer extends KotlinRenderer {
// and enums in the same union
(_enumType) => "is String",
(_unionType) => mustNotHappen(),
(_transformedStringType) => "is String",
);
}

Expand Down Expand Up @@ -150,7 +166,17 @@ export class KotlinKlaxonRenderer extends KotlinRenderer {
this.typeGraph.allNamedTypes(),
(c) => c instanceof ClassType && c.getProperties().size === 0,
);
if (hasUnions || this.haveEnums || hasEmptyObjects) {
const usesDateTime = this.haveTransformedStringType("date-time");
const usesDate = this.haveTransformedStringType("date");
const usesTime = this.haveTransformedStringType("time");
if (
hasUnions ||
this.haveEnums ||
hasEmptyObjects ||
usesDateTime ||
usesDate ||
usesTime
) {
this.emitGenericConverter();
}

Expand All @@ -160,6 +186,36 @@ export class KotlinKlaxonRenderer extends KotlinRenderer {
}

const converters: Sourcelike[][] = [];
if (usesDateTime) {
converters.push([
[".convert(OffsetDateTime::class,"],
[" { OffsetDateTime.parse(it.string!!) },"],
[
' { "\\"${java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(it)}\\"" })',
],
]);
}

if (usesDate) {
converters.push([
[".convert(LocalDate::class,"],
[" { LocalDate.parse(it.string!!) },"],
[
' { "\\"${java.time.format.DateTimeFormatter.ISO_LOCAL_DATE.format(it)}\\"" })',
],
]);
}

if (usesTime) {
converters.push([
[".convert(OffsetTime::class,"],
[" { OffsetTime.parse(it.string!!) },"],
[
' { "\\"${java.time.format.DateTimeFormatter.ISO_OFFSET_TIME.format(it)}\\"" })',
],
]);
}

if (hasEmptyObjects) {
converters.push([
[".convert(JsonObject::class,"],
Expand Down Expand Up @@ -455,25 +511,60 @@ export class KotlinKlaxonRenderer extends KotlinRenderer {
" = when (jv.inside) {",
);
this.indent(() => {
const table: Sourcelike[][] = [];
// Members whose JSON representations share a value type
// (several transformed string types, or a transformed string
// type and an enum, are all strings) must share a single
// guard and be tried in sequence, most specific parse first.
const groups: Array<{
guard: string;
members: Array<{ name: Name; t: Type }>;
}> = [];
this.forEachUnionMember(
u,
nonNulls,
"none",
null,
(name, t) => {
table.push([
[this.unionMemberJsonValueGuard(t, "jv.inside")],
[
" -> ",
name,
"(",
this.unionMemberFromJsonValue(t, "jv"),
"!!)",
],
]);
const guard = this.sourcelikeToString(
this.unionMemberJsonValueGuard(t, "jv.inside"),
);
const group = groups.find((g) => g.guard === guard);
if (group === undefined) {
groups.push({ guard, members: [{ name, t }] });
} else {
group.members.push({ name, t });
}
},
);
const table: Sourcelike[][] = [];
for (const { guard, members } of groups) {
const ordered = [...members].sort(
(a, b) =>
unionMemberMatchPriority(a.t) -
unionMemberMatchPriority(b.t),
);
const last = ordered[ordered.length - 1];
let expr: Sourcelike = [
last.name,
"(",
this.unionMemberFromJsonValue(last.t, "jv"),
"!!)",
];
for (let i = ordered.length - 2; i >= 0; i--) {
expr = [
"try { ",
ordered[i].name,
"(",
this.unionMemberFromJsonValue(ordered[i].t, "jv"),
"!!) } catch (e: Exception) { ",
expr,
" }",
];
}

table.push([[guard], [" -> ", expr]]);
}

if (maybeNull !== null) {
const name = this.nameForUnionMember(u, maybeNull);
table.push([
Expand Down
Loading