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; } = "";
}
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,
}
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"
}
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);
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)