DEV Community

Ansari Aquib
Ansari Aquib

Posted on Originally published at bytekiln.com AI-assisted

Keep ASP.NET Core DTOs and TypeScript Interfaces in Sync


An ASP.NET Core API exposes a C# DTO. A React, Angular, or Next.js frontend consumes the response. Somewhere between them, someone writes a TypeScript interface that looks almost right.

The dangerous word is "almost." A DateTime isn't a JavaScript Date in a JSON response, Guid isn't a special TypeScript primitive, and property casing can differ from the C# class.

1. Define an API response model

using System.Text.Json.Serialization;

public enum PaymentStatus
{
    Pending = 0,
    Completed = 1,
    Failed = 2
}

public class PaymentResponse
{
    public Guid Id { get; set; }
    public decimal Amount { get; set; }
    public DateTime CreatedAt { get; set; }
    public PaymentStatus Status { get; set; }
    public string? FailureReason { get; set; }

    [JsonPropertyName("merchant_ref")]
    public string MerchantReference { get; set; } = "";

    [JsonIgnore]
    public string InternalNote { get; set; } = "";
}
Enter fullscreen mode Exit fullscreen mode

Suppose the API uses the default web-oriented System.Text.Json configuration. Id is emitted as id, CreatedAt as createdAt, and MerchantReference as merchant_ref. InternalNote is not serialized.

2. Generate a TypeScript starting point

Open the ByteKiln C# to TypeScript Converter and paste the declaration including the enum. The converter understands classes, records, enums, nullable properties, generics, collections, and JSON naming attributes.

A representative TypeScript contract for the JSON shown above is:

export interface PaymentResponse {
  id: string;
  amount: number;
  createdAt: string;
  status: PaymentStatus;
  failureReason: string | null;
  merchant_ref: string;
}

export enum PaymentStatus {
  Pending = 0,
  Completed = 1,
  Failed = 2,
}
Enter fullscreen mode Exit fullscreen mode

The exact emitted syntax depends on your selected output and nullability options. This is an example of the contract to check against.

3. Why matching the wire format matters

Here's the response your frontend might receive:

{
  "id": "0f40632d-25e5-4d41-8501-19c1e15021d7",
  "amount": 249.50,
  "createdAt": "2026-10-01T12:00:00Z",
  "status": 1,
  "failureReason": null,
  "merchant_ref": "ORDER-1089"
}
Enter fullscreen mode Exit fullscreen mode

A frontend shouldn't assume status is a string enum unless the .NET serializer is configured to emit string values. Likewise, createdAt is a string until you explicitly parse it:

const createdDate = new Date(payment.createdAt);
Enter fullscreen mode Exit fullscreen mode

long/ulong values also need special attention: TypeScript's number cannot precisely represent every 64-bit integer. Consider exposing large IDs as strings.

4. Review the contract, not just the type checker

A generator cannot know every runtime serializer setting. Verify camel-case behavior, custom converters, [JsonIgnore], and enum serialization. For critical endpoints, snapshot a JSON response or add integration tests to ensure the frontend types remain accurate.

Try it: C# to TypeScript Converter — ByteKiln. It's particularly handy when a .NET backend serves a JavaScript frontend and there's no shared schema-generation pipeline yet.

Top comments (0)