Skip to content
Merged
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 @@ -512,8 +512,11 @@ export class SystemTextJsonCSharpRenderer extends CSharpRenderer {
}
}

this.emitLine("new DateOnlyConverter(),");
this.emitLine("new TimeOnlyConverter(),");
if (this._options.dateTimeOnlyConverters) {
this.emitLine("new DateOnlyConverter(),");
this.emitLine("new TimeOnlyConverter(),");
}

this.emitLine("IsoDateTimeOffsetConverter.Singleton");
// this.emitLine("new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }");
});
Expand Down Expand Up @@ -1305,7 +1308,16 @@ export class SystemTextJsonCSharpRenderer extends CSharpRenderer {
this.forEachTransformation("leading-and-interposing", (n, t) =>
this.emitTransformation(n, t),
);
this.emitMultiline(`
if (this._options.dateTimeOnlyConverters) {
this.emitDateTimeOnlyConverters();
}

this.emitIsoDateTimeOffsetConverter();
}
}

private emitDateTimeOnlyConverters(): void {
this.emitMultiline(`
public class DateOnlyConverter : JsonConverter<DateOnly>
{
private readonly string serializationFormat;
Expand Down Expand Up @@ -1345,9 +1357,12 @@ public class TimeOnlyConverter : JsonConverter<TimeOnly>

public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToString(serializationFormat));
}
}`);
}

internal class IsoDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
private emitIsoDateTimeOffsetConverter(): void {
this.ensureBlankLine();
this.emitMultiline(`internal class IsoDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
public override bool CanConvert(Type t) => t == typeof(DateTimeOffset);

Expand Down Expand Up @@ -1415,7 +1430,6 @@ internal class IsoDateTimeOffsetConverter : JsonConverter<DateTimeOffset>

public static readonly IsoDateTimeOffsetConverter Singleton = new IsoDateTimeOffsetConverter();
}`);
}
}

protected needNamespace(): boolean {
Expand Down
14 changes: 11 additions & 3 deletions packages/quicktype-core/src/language/CSharp/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,15 @@ export const cSharpOptions = {

export const newtonsoftCSharpOptions = { ...cSharpOptions };

export const systemTextJsonCSharpOptions = { ...cSharpOptions };
export const systemTextJsonCSharpOptions = {
...cSharpOptions,
dateTimeOnlyConverters: new BooleanOption(
"dateonly-timeonly-converters",
"Emit DateOnly/TimeOnly converters (requires .NET 6 or later)",
true,
"secondary",
),
};

export const cSharpLanguageConfig = {
displayName: "C#",
Expand All @@ -148,8 +156,8 @@ export class CSharpTargetLanguage extends TargetLanguage<
super(cSharpLanguageConfig);
}

public getOptions(): typeof cSharpOptions {
return cSharpOptions;
public getOptions(): typeof systemTextJsonCSharpOptions {
return systemTextJsonCSharpOptions;
}

public get stringTypeMapping(): StringTypeMapping {
Expand Down
3 changes: 3 additions & 0 deletions test/languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ export const CSharpLanguageSystemTextJson: Language = {
{ density: "dense" },
{ "number-type": "decimal" },
{ "any-type": "dynamic" },
// Suppressing the DateOnly/TimeOnly converters (for pre-.NET 6
// targets) must still produce compiling, round-tripping code.
["unions.json", { "dateonly-timeonly-converters": "false" }],
],
sourceFiles: ["src/language/CSharp/index.ts"],
};
Expand Down
59 changes: 59 additions & 0 deletions test/unit/csharp-dateonly-timeonly-converters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
InputData,
JSONSchemaInput,
type RendererOptions,
quicktype,
} from "../../packages/quicktype-core/src/index.js";
import { describe, expect, test } from "vitest";

async function renderCSharp(
rendererOptions: RendererOptions = {},
): Promise<string> {
const schema = {
type: "object",
properties: {
name: { type: "string" },
count: { type: "integer" },
},
required: ["name", "count"],
};

const schemaInput = new JSONSchemaInput(undefined);
await schemaInput.addSource({
name: "TopLevel",
schema: JSON.stringify(schema),
});

const inputData = new InputData();
inputData.addInput(schemaInput);

const result = await quicktype({
inputData,
lang: "csharp",
rendererOptions: { framework: "SystemTextJson", ...rendererOptions },
});
return result.lines.join("\n");
}

describe("C# System.Text.Json DateOnly/TimeOnly converters", () => {
test("emits the converters by default", async () => {
const output = await renderCSharp();

expect(output).toContain("class DateOnlyConverter");
expect(output).toContain("class TimeOnlyConverter");
expect(output).toContain("new DateOnlyConverter(),");
expect(output).toContain("new TimeOnlyConverter(),");
});

test("omits the converters with dateonly-timeonly-converters=false", async () => {
const output = await renderCSharp({
"dateonly-timeonly-converters": "false",
});

expect(output).not.toContain("DateOnlyConverter");
expect(output).not.toContain("TimeOnlyConverter");
// The DateTimeOffset converter is unaffected.
expect(output).toContain("class IsoDateTimeOffsetConverter");
expect(output).toContain("IsoDateTimeOffsetConverter.Singleton");
});
});
Loading