TypeScript Support - atdts

This documentation is incomplete. Your help would be appreciated! In particular, some how-to guides would be great.

Tutorials

Hello World

Install atdts with opam:

opam install atdts

Create a file hello.atd containing this:

type message = {
  subject: string;
  body: string;
}

Call atdts to produce hello.ts:

$ atdts hello.atd

There’s now a file hello.ts that contains a class looking like this:

...

export type Message = {
  subject: string;
  body: string;
}

export function writeMessage(x: Message, context: any = x): any {
  ...
}

export function readMessage(x: any, context: any = x): Message {
  ...
}

...

Let’s write a TypeScript program say_hello.ts that uses this code:

import * as hello from "./hello"

const msg: hello.Message = {
  subject: "Hello",
  body: "Dear friend, I hope you are well."
}

console.log(JSON.stringify(hello.writeMessage(msg)))

Running it will print the JSON message:

$ tsc --lib es2017,dom say_hello.ts
{"subject":"Hello","body":"Dear friend, I hope you are well."}

Such JSON data can be parsed. Let’s write a program read_message.ts that consumes JSON data from standard input:

import * as hello from "./hello"
import * as readline from "readline"

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})

rl.question('', (data: string) => {
  const msg = hello.readMessage(JSON.parse(data))
  console.log("subject: " + msg.subject)
})

Output:

# Install dependencies
$ npm install --save-dev @types/node
$ npm install readline

# Compile
$ tsc --lib es2017,dom read_message.ts

# Run
$ echo '{"subject": "big news", "body": ""}' | js read_message.js
subject: big news

It works! But what happens if the JSON data lacks a "subject" field? Let’s see:

$ echo '{"body": ""}' | js read_message.js
{"body": ""}
readline.js:1086
            throw err;
            ^

Error: missing field 'subject' in JSON object of type 'Message'
...

And what if our program also thought that the correct field name was subj rather than subject? Here’s read_message_wrong.ts which tries to access a subj field:

import * as hello from "./hello"
import * as readline from "readline"

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})

rl.question('', (data: string) => {
  const msg = hello.readMessage(JSON.parse(data))
  console.log("subject: " + msg.subj)
})

Let’s compile our program:

$ tsc --lib es2017,dom read_message_wrong.ts
read_message_wrong.ts:11:33 - error TS2339: Property 'subj' does not exist on type 'Message'.

11   console.log("subject: " + msg.subj)
                                   ~~~~


Found 1 error in read_message_wrong.ts:11

The typechecker detected that our program makes incorrect assumptions about the message format without running it.

ATD Records, JSON objects, TypeScript objects

An ATD file contains types that describe the structure of JSON data. JSON objects map to TypeScript types and objects. They’re called records in the ATD language. Let’s define a simple record type in the file hello_plus.atd:

type message = {
  subject: string;
  ~body: string;
}

Note the ~ in front of the body field. It means that this field has a default value. Whenever the JSON field is missing from a JSON object, a default value is assumed. The implicit default value for a string is "".

Let’s add a signature field whose default value isn’t the empty string:

type message = {
  subject: string;
  ~body: string;
  ~signature <ts default="'anonymous'">: string;
}

Finally, we’ll add an optional url field that doesn’t take a default value at all:

type message = {
  subject: string;
  ~body: string;
  ~signature <ts default="'anonymous'">: string;
  ?url: string option;
}

Let’s generate the TypeScript code for this.

$ atdts hello_plus.atd

Let’s update our reader program read_message_plus.ts to this:

import * as hello_plus from "./hello_plus"
import * as readline from "readline"

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
})

rl.question('', (data: string) => {
  const msg = hello_plus.readMessage(JSON.parse(data))
  console.log(msg)
})

We can test it, showing us the final value of each field:

$ tsc --lib es2017,dom read_message_plus.ts
$ echo '{"subject":"hi"}' | js read_message_plus.js
{"subject":"hi"}
{ subject: 'hi',
  body: '',
  signature: 'anonymous',
  url: undefined }

How-to guides

Defining default field values

[missing]

Renaming field names

[missing]

Deep dives

[missing]

Reference

Type mapping

ATD type TypeScript type JSON example
unit null null
bool bool True
int Int* 42 or 42.0
float number 6.28
string string "Hello"
string list string[] ["a", "b", "c!"]
(bool * float) [boolean, number] [-1, 1]
int nullable Int | null 42 or null
abstract any anything
{ id: string } { id: string } {"id": "3hj8d"}
[A | B of int] {kind: 'A'} | {kind: 'B', value: Int} "A" or ["B", 5]
foo_bar FooBar  

*the Int type is an alias for number but additionally, the read and write functions generated by atdts check that the number is a whole number.

Supported ATD annotations

Default field values

Record fields following a ~ assume a default value. The default value can be implicit as mandated by the ATD language specification (false for bool, zero for int, etc.) or it can be a user-provided value.

A user-provided default uses an annotation of the form <ts default="VALUE"> where VALUE evaluates to a TypeScript expression e.g.

type foo = {
  ~answer <ts default="42">: int;
}

For example, the JSON value {} will be read as {answer: 42}.

Field and constructor renaming

Alternate JSON object field names can be specified using an annotation of the form <json name="NAME"> where NAME is the desired field name to be used in the JSON representation. For example, the following specifies the JSON name of the id field is ID:

type foo = {
  id <json name="ID">: string
}

Similarly, the constructor names of sum types can also be given alternate names in the JSON representation. Here’s an example:

type bar = [
| Alpha <json name="alpha">
| Beta <json name="beta"> of int
]

Alternate representations for association lists

List of pairs can be represented by JSON objects or by TypeScript maps if the correct annotations are provided:

  • (string * bar) list <json repr="object"> will use JSON objects to represent a list of pairs of TypeScript type [string, Bar][]. Using the annotation <json repr="array"> is equivalent to the default.
  • (foo * bar) list <ts repr="map"> will use a TypeScript map of type Map<Foo, Bar> to represent the association list. Using the annotation <ts repr="array"> is equivalent to the default.