Skip to content

Commit

Permalink
TypeScript generator: Use string type as the discriminator property t…
Browse files Browse the repository at this point in the history
…ype in specialized interfaces (#1718)

* improve TypeScript interface generation

* TypeScript generator: Use string type as the discriminator property type in specialized interfaces

* fix tests
  • Loading branch information
RicoSuter authored Jul 17, 2024
1 parent 9545eed commit 9bf8f69
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/NJsonSchema.CodeGeneration.Tests/EnumGenerationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ public async Task When_enum_has_string_value_then_TS_code_has_string_value()
var code = generator.GenerateFile("MyClass");

//// Assert
Assert.Contains("_0562 = <any>\"0562\",", code);
Assert.Contains("_0532 = <any>\"0532\",", code);
Assert.Contains("_0562 = \"0562\",", code);
Assert.Contains("_0532 = \"0532\",", code);
}

public class ClassWithStringEnum
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//----------------------
// <auto-generated>
// </auto-generated>
//----------------------







export interface Base {
Type: EBase;
}

export enum EBase {
OneChild = "OneChild",
SecondChild = "SecondChild",
}

export interface OneChild extends Base {
A: string;
Type: EBase.OneChild;
}

export interface SecondChild extends Base {
B: string;
Type: EBase.SecondChild;
}

export interface MyClass {
Child: Base;
Children: Base[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//----------------------
// <auto-generated>
// </auto-generated>
//----------------------







export interface Base {
Type: EBase;
}

export type EBase = "OneChild" | "SecondChild";

export interface OneChild extends Base {
A: string;
Type: 'OneChild';
}

export interface SecondChild extends Base {
B: string;
Type: 'SecondChild';
}

export interface MyClass {
Child: Base;
Children: Base[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//----------------------
// <auto-generated>
// </auto-generated>
//----------------------







export abstract class Base implements IBase {

protected _discriminator: string;

constructor(data?: IBase) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
this._discriminator = "Base";
}

init(_data?: any) {
}

static fromJS(data: any): Base {
data = typeof data === 'object' ? data : {};
if (data["Type"] === "OneChild") {
let result = new OneChild();
result.init(data);
return result;
}
if (data["Type"] === "SecondChild") {
let result = new SecondChild();
result.init(data);
return result;
}
throw new Error("The abstract class 'Base' cannot be instantiated.");
}

toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["Type"] = this._discriminator;
return data;
}
}

export interface IBase {
}

export class OneChild extends Base implements IOneChild {
a: string;
type: EBase;

constructor(data?: IOneChild) {
super(data);
this._discriminator = "OneChild";
}

init(_data?: any) {
super.init(_data);
if (_data) {
this.a = _data["A"];
this.type = _data["Type"];
}
}

static fromJS(data: any): OneChild {
data = typeof data === 'object' ? data : {};
let result = new OneChild();
result.init(data);
return result;
}

toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["A"] = this.a;
data["Type"] = this.type;
super.toJSON(data);
return data;
}
}

export interface IOneChild extends IBase {
a: string;
type: EBase;
}

export enum EBase {
OneChild = "OneChild",
SecondChild = "SecondChild",
}

export class SecondChild extends Base implements ISecondChild {
b: string;
type: EBase;

constructor(data?: ISecondChild) {
super(data);
this._discriminator = "SecondChild";
}

init(_data?: any) {
super.init(_data);
if (_data) {
this.b = _data["B"];
this.type = _data["Type"];
}
}

static fromJS(data: any): SecondChild {
data = typeof data === 'object' ? data : {};
let result = new SecondChild();
result.init(data);
return result;
}

toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["B"] = this.b;
data["Type"] = this.type;
super.toJSON(data);
return data;
}
}

export interface ISecondChild extends IBase {
b: string;
type: EBase;
}

export class MyClass implements IMyClass {
child: OneChild | SecondChild;
children: (OneChild | SecondChild)[];

constructor(data?: IMyClass) {
if (data) {
for (var property in data) {
if (data.hasOwnProperty(property))
(<any>this)[property] = (<any>data)[property];
}
}
}

init(_data?: any) {
if (_data) {
this.child = _data["Child"] ? OneChild | SecondChild.fromJS(_data["Child"]) : <any>undefined;
if (Array.isArray(_data["Children"])) {
this.children = [] as any;
for (let item of _data["Children"])
this.children.push(OneChild | SecondChild.fromJS(item));
}
}
}

static fromJS(data: any): MyClass {
data = typeof data === 'object' ? data : {};
let result = new MyClass();
result.init(data);
return result;
}

toJSON(data?: any) {
data = typeof data === 'object' ? data : {};
data["Child"] = this.child ? this.child.toJSON() : <any>undefined;
if (Array.isArray(this.children)) {
data["Children"] = [];
for (let item of this.children)
data["Children"].push(item.toJSON());
}
return data;
}
}

export interface IMyClass {
child: OneChild | SecondChild;
children: (OneChild | SecondChild)[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//----------------------
// <auto-generated>
// </auto-generated>
//----------------------







export interface Base {
Type: string;
}

export interface OneChild extends Base {
A: string;
Type: EBase.OneChild;
}

export enum EBase {
OneChild = "OneChild",
SecondChild = "SecondChild",
}

export interface SecondChild extends Base {
B: string;
Type: EBase.SecondChild;
}

export interface MyClass {
Child: OneChild | SecondChild;
Children: (OneChild | SecondChild)[];
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using NJsonSchema.Generation;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Xunit;
using NJsonSchema.NewtonsoftJson.Converters;
using NJsonSchema.NewtonsoftJson.Generation;
using VerifyXunit;
using Newtonsoft.Json.Converters;

namespace NJsonSchema.CodeGeneration.TypeScript.Tests
{
[UsesVerify]
public class TypeScriptDiscriminatorTests
{
[JsonConverter(typeof(JsonInheritanceConverter), nameof(Type))]
Expand All @@ -19,6 +21,7 @@ public abstract class Base
public abstract EBase Type { get; }
}

[JsonConverter(typeof(StringEnumConverter))]
public enum EBase
{
OneChild,
Expand Down Expand Up @@ -55,6 +58,7 @@ public async Task When_generating_interface_contract_add_discriminator()
GenerateAbstractProperties = true,
});
var data = schema.ToJson();
var json = JsonConvert.SerializeObject(new OneChild());

//// Act
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
Expand All @@ -66,8 +70,9 @@ public async Task When_generating_interface_contract_add_discriminator()

//// Assert
Assert.Contains("export interface Base {\n Type: EBase;\n}", code);
await VerifyHelper.Verify(code);
}

[Fact]
public async Task When_generating_interface_contract_add_discriminator_string_literal()
{
Expand All @@ -89,6 +94,7 @@ public async Task When_generating_interface_contract_add_discriminator_string_li

//// Assert
Assert.Contains("export interface Base {\n Type: EBase;\n}", code);
await VerifyHelper.Verify(code);
}

[Fact]
Expand All @@ -112,8 +118,9 @@ public async Task When_parameter_is_abstract_then_generate_union_interface()
Assert.Contains("export interface SecondChild extends Base", code);
Assert.Contains("Child: OneChild | SecondChild;", code);
Assert.Contains("Children: (OneChild | SecondChild)[];", code);
await VerifyHelper.Verify(code);
}

[Fact]
public async Task When_parameter_is_abstract_then_generate_union_class()
{
Expand All @@ -135,6 +142,7 @@ public async Task When_parameter_is_abstract_then_generate_union_class()
Assert.Contains("export class SecondChild extends Base", code);
Assert.Contains("child: OneChild | SecondChild;", code);
Assert.Contains("children: (OneChild | SecondChild)[];", code);
await VerifyHelper.Verify(code);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ public List<EnumerationItemModel> Enums
entries.Add(new EnumerationItemModel
{
Name = _settings.EnumNameGenerator.Generate(i, name, value, _schema),
Value = _schema.Type.IsInteger() ?
value.ToString() :
(_settings.TypeScriptVersion < 2.4m ? "<any>" : "") + "\"" + value + "\"",
Value = _schema.Type.IsInteger() ? value.ToString() : "\"" + value + "\"",
});
}
}
Expand Down
Loading

0 comments on commit 9bf8f69

Please sign in to comment.