Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Typescript

Generate TypeScript Interfaces from POCO Classes

5.00/5 (4 votes)
18 Sep 2018CPOL2 min read 16.6K   212  
Generate TypeScript Interfaces from POCO Classes of .NET Framework or .NET Core

Introduction

POCO2TS is a command line program that generates TypeScript interfaces from POCO classes of assemblies built on .NET Framework or .NET Core. It does not matter if your .NET codes are in C# or VB.

Background

This is a by-product of WebApiClientGen. If you are developing Web client programs using  jQuery or Angular 2+, you may want more than what POCO2TS offers, which is actually integrated in WebApiClientGen. If so, you may skip this article and instead, read the following:

Using the Code

You have POCO classes in assembly DemoWebApi.DemoData.dll, which are decorated by DataContractAttribute, to be used for data models, WCF, Entity Framework Code First and serialization with XML or JSON. Check this example in C#.

C#
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Collections.ObjectModel;

namespace DemoWebApi.DemoData
{
    public sealed class Constants
    {
        public const string DataNamespace = "http://fonlow.com/DemoData/2014/02";
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public enum AddressType
    {
        [EnumMember]
        Postal,
        [EnumMember]
        Residential,
    };
 
    public enum MyEnumType
    {
        [EnumMember]
        First = 1,
        [EnumMember]
        Two = 2,
    };

    [DataContract(Namespace = Constants.DataNamespace)]
    public enum Days
    {
        [EnumMember]
        Sat = 1,
        [EnumMember]
        Sun,
        [EnumMember]
        Mon,
        [EnumMember]
        Tue,
        [EnumMember]
        Wed,
        /// <summary>
        /// Thursday
        /// </summary>
        [EnumMember]
        Thu,
        [EnumMember]
        Fri
    };

    [DataContract(Namespace = Constants.DataNamespace)]
    public class PhoneNumber
    {
        public PhoneNumber()
        {

        }

        public Guid Id { get; set; }

        [DataMember]
        public string FullNumber { get; set; }

        [DataMember]
        public PhoneType PhoneType { get; set; }

        public Guid EntityId { get; set; }
    }

    /// <summary>
    /// Phone type
    /// Tel, Mobile, Skyp and Fax
    ///
    /// </summary>
    [DataContract(Namespace = Constants.DataNamespace)]
    public enum PhoneType
    {
        /// <summary>
        /// Land line
        /// </summary>
        [EnumMember]
        Tel = 0,

        /// <summary>
        /// Mobile phone
        /// </summary>
        [EnumMember]
        Mobile = 1,

        [EnumMember]
        Skype = 2,
        [EnumMember]
        Fax = 3,
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Address
    {
        [DataMember]
        public Guid Id { get; set; }

        public Entity Entity { get; set; }

        /// <summary>
        /// Foreign key to Entity
        /// </summary>
        public Guid EntityId { get; set; }

        [DataMember]
        public string Street1 { get; set; }

        [DataMember]
        public string Street2 { get; set; }

        [DataMember]
        public string City { get; set; }

        [DataMember]
        public string State { get; set; }

        [DataMember]
        public string PostalCode { get; set; }

        [DataMember]
        public string Country { get; set; }

        [DataMember]
        public AddressType Type { get; set; }

        [DataMember]
        public DemoWebApi.DemoData.Another.MyPoint Location;
    }

    /// <summary>
    /// Base class of company and person
    /// </summary>
    [DataContract(Namespace = Constants.DataNamespace)]
    public class Entity
    {
        public Entity()
        {
            Addresses = new List<Address>();
        }

        [DataMember]
        public Guid Id { get; set; }

        /// <summary>
        /// Name of the entity.
        /// </summary>
        [DataMember(IsRequired =true)]//MVC and Web API does not care
        [System.ComponentModel.DataAnnotations.Required]//MVC and Web API care about only this
        public string Name { get; set; }

        /// <summary>
        /// Multiple addresses
        /// </summary>
        [DataMember]
        public IList<Address> Addresses { get; set; }

        [DataMember]
        public virtual ObservableCollection<PhoneNumber> PhoneNumbers { get; set; }

        public override string ToString()
        {
            return Name;
        }

        [DataMember]
        public Uri Web { get; set; }
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Person : Entity
    {
        [DataMember]
        public string Surname { get; set; }
        [DataMember]
        public string GivenName { get; set; }

        /// <summary>
        /// Date of Birth.
        /// This is optional.
        /// </summary>
        [DataMember]
        public DateTime? DOB { get; set; }

        public override string ToString()
        {
            return Surname + ", " + GivenName;
        }
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public class Company : Entity
    {
        /// <summary>
        /// BusinessNumber to be serialized as BusinessNum
        /// </summary>
        [DataMember(Name ="BusinessNum")]
        public string BusinessNumber { get; set; }

        [DataMember]
        public string BusinessNumberType { get; set; }

        [DataMember]
        public string[][] TextMatrix
        { get; set; }

        [DataMember]
        public int[][] Int2DJagged;

        [DataMember]
        public int[,] Int2D;

        [DataMember]
        public IEnumerable<string> Lines;
    }

    [DataContract(Namespace = Constants.DataNamespace)]
    public class MyPeopleDic
    {
        [DataMember]
        public IDictionary<string, Person> Dic { get; set; }

        [DataMember]
        public IDictionary<string, string> AnotherDic { get; set; }

        [DataMember]
        public IDictionary<int, string> IntDic { get; set; }     
    }
}

After running:

BAT
POCO2TS.exe demowebapi.demodata.dll TypeScriptDataModels.ts

or:

BAT
dotnet POCO2TSCore.dll demowebapi.demodatacore.dll TypeScriptDataModels.ts

The output TypeScriptDataModels.ts will contain TypeScript interfaces:

JavaScript
export namespace DemoWebApi_DemoData_Client {
    export enum AddressType { Postal, Residential }

    export enum Days {
        Sat = 1,
        Sun = 2,
        Mon = 3,
        Tue = 4,
        Wed = 5,
        
        /**
         * Thursday
         */
        Thu = 6,
        Fri = 7
    }

    export interface PhoneNumber {
        fullNumber?: string;
        phoneType?: DemoWebApi_DemoData_Client.PhoneType;
    }

    /**
     * Phone type
     * Tel, Mobile, Skyp and Fax
     */
    export enum PhoneType {
        
        /**
         * Land line
         */
        Tel,
        
        /**
         * Mobile phone
         */
        Mobile,
        Skype,
        Fax
    }

    export interface Address {
        id?: string;
        street1?: string;
        street2?: string;
        city?: string;
        state?: string;
        postalCode?: string;
        country?: string;
        type?: DemoWebApi_DemoData_Client.AddressType;
        location?: any;
    }

    /**
     * Base class of company and person
     */
    export interface Entity {
        id?: string;

        /**
         * Name of the entity.
         */
        name: string;

        /**
         * Multiple addresses
         */
        addresses?: Array<DemoWebApi_DemoData_Client.Address>;
        phoneNumbers?: Array<DemoWebApi_DemoData_Client.PhoneNumber>;
        web?: string;
    }

    export interface Person extends DemoWebApi_DemoData_Client.Entity {
        surname?: string;
        givenName?: string;

        /**
         * Date of Birth.
         * This is optional.
         */
        dob?: Date;
    }

    export interface Company extends DemoWebApi_DemoData_Client.Entity {

        /**
         * BusinessNumber to be serialized as BusinessNum
         */
        BusinessNum?: string;
        businessNumberType?: string;
        textMatrix?: Array<Array<string>>;
        int2DJagged?: Array<Array<number>>;
        int2D?: number[][];
        lines?: Array<string>;
    }

    export interface MyPeopleDic {
        dic?: {[id: string]: DemoWebApi_DemoData_Client.Person };
        anotherDic?: {[id: string]: string };
        intDic?: {[id: number]: string };
    }
}

POCO2TS.exe as a command line program by default will just process all classes decorated by DataContractAttribute and publish those members decorated by DataMemberAttribute. However, members of enum types will be all processed, regardless of EnumMemberAttribute.

Foreign keys in Code First generally should not be exposed to client programs, and they are not decorated by DataMemberAttribute, thus they are not included in the generated TypeScript interfaces.

Cherry Picking

As you can see, POCO2TS uses primarily attributes to do cherry picking. There are multiple methods of cherry picking. If you run POCO2TS.exe without parameters, you may see the following:

BAT
Poco2Ts.exe generates TypeScript data model interfaces from POCO classes.
Example:
For classes decorated by DataContractAttribute:
  Fonlow.Poco2Ts.exe MyAssemblyWithPOCO.dll MyOutputTS.ts
For classes decorated by Newtonsoft.Json.JsonObjectAttribute:
  Fonlow.Poco2Ts.exe MyAssemblyWithPOCO.dll MyOutputTS.ts /2
For classes decorated by SerializableAttribute:
  Fonlow.Poco2Ts.exe MyAssemblyWithPOCO.dll MyOutputTS.ts /4
For public classes, properties and properties,  
and use System.ComponentModel.DataAnnotations.RequiredAttribute:
  Fonlow.Poco2Ts.exe MyAssemblyWithPOCO.dll MyOutputTS.ts /8
For all classes, properties and fields
  Fonlow.Poco2Ts.exe MyAssemblyWithPOCO.dll MyOutputTS.ts /0

So if you somehow have some classes decorated by DataContractAttribute and others by JsonObjectAttribute, and you want to include both sets, then the option is /3. And DataMemberAttribute and JsonPropertyAttribute will be handled accordingly.

Points of Interest

POCO2TS does not really have static dependency on Newtonsoft.Json. Only when you use some attribute classes like JsonObjectAttribute of NewtonSoft.Json to decorate your POCO classes and would use such attribute classes for cherry picking, Newtonsoft.Json is needed. When you use the NewtonsoftJson option, .NET runtime will try to locate it. If the runtime fails to locate it, POCO2TS will resolve through checking the folder where the data model assembly is located. This resolution is also useful when your data model assembly has dependency on the other data model assembly.

You may have come across TypeLITE and TypeWriter, and there's a simple comparison: Compare with TypeLITE and TypeWriter.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)