Header menu logo Fabulous.AST

Build-Time Type Generation

Automatically generate F# types from JSON files at build time using Fabulous.AST.Build.

This extension provides an MSBuild task that watches JSON files and generates F# record types during compilation.

Installation

dotnet add package Fabulous.AST.Build

💡 Tip: For programmatic control over generation, see the JSON Extension tutorial.

Quick Start

1. Add a JSON schema file

Create schemas/user.json:

{
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com",
    "isActive": true
}

2. Configure your project

Add to your .fsproj:

<ItemGroup>
  <FabulousAstJson Include="schemas/user.json" />
</ItemGroup>

<ItemGroup>
  <Compile Include="Generated/user.Generated.fs" />
</ItemGroup>

3. Build

dotnet build

This generates Generated/user.Generated.fs:

type Root = {
    id: int
    name: string
    email: string
    isActive: bool
}

Configuration Options

Custom Root Type Name

Override the default Root type name:

<FabulousAstJson Include="schemas/user.json" RootName="User" />

Add a Module

Wrap types in a file-level module:

<FabulousAstJson Include="schemas/user.json"
                 RootName="User"
                 ModuleName="MyApp.Models" />

Generates:

module MyApp.Models

type User = { id: int; name: string; email: string; isActive: bool }

Note: Each file must have a unique module name since file-level modules cannot share the same fully-qualified name.

Custom Output Directory

Change the output location:

<PropertyGroup>
  <FabulousAstJsonOutputDir>Types/</FabulousAstJsonOutputDir>
</PropertyGroup>

Custom Output Filename

Override the default {name}.Generated.fs pattern:

<FabulousAstJson Include="schemas/user.json" OutputFileName="UserTypes.fs" />

Multiple Files

Individual Configuration

<ItemGroup>
  <FabulousAstJson Include="schemas/user.json"
                   RootName="User"
                   ModuleName="MyApp.Models.User" />
  <FabulousAstJson Include="schemas/product.json"
                   RootName="Product"
                   ModuleName="MyApp.Models.Product" />
</ItemGroup>

<ItemGroup>
  <Compile Include="Generated/user.Generated.fs" />
  <Compile Include="Generated/product.Generated.fs" />
</ItemGroup>

Glob Patterns

Process all JSON files in a directory:

<ItemGroup>
  <FabulousAstJson Include="schemas/**/*.json" />
</ItemGroup>

Type Inference Examples

Nested Objects

Input (company.json):

{
    "name": "Acme Corp",
    "address": {
        "street": "123 Main St",
        "city": "London"
    }
}

Output:

type Address = { street: string; city: string }
type Company = { name: string; address: Address }

Arrays

Input (users.json):

[
    { "id": 1, "name": "Alice" },
    { "id": 2, "name": "Bob" }
]

Output:

type UsersItem = { id: int; name: string }
type Users = UsersItem list

Optional Fields

Fields missing in some array objects become option types:

Input:

[
    { "id": 1, "name": "Alice", "nickname": "Ali" },
    { "id": 2, "name": "Bob" }
]

Output:

type RootItem = { id: int; name: string; nickname: string option }
type Root = RootItem list

Type Inference Rules

JSON Value

F# Type

"string"

string

123

int

9999999999

int64

123.45

float

true/false

bool

null

obj

[...]

ElementType list

{...}

Record type

Special Field Name Handling

Leading Digits

Fields starting with digits are prefixed with _:

{ "2faEnabled": true }

Generates: _2faEnabled: bool

Reserved Keywords

F# keywords are escaped with double backticks:

{ "type": "admin" }

Generates: ``type``: string

Configuration Reference

Project Properties

|| Property | Default | Description | ||----------|---------|-------------| || FabulousAstJsonOutputDir | Generated/ | Output directory | || EnableFabulousAstJsonGeneration | true | Enable/disable generation (set to false to skip) |

Note: EnableFabulousAstJsonGeneration defaults to true, so you only need to set it explicitly if you want to disable generation.

Item Metadata

|| Metadata | Default | Description | ||----------|---------|-------------| || RootName | Root | Root type name | || ModuleName | (empty) | File-level module name (e.g., MyApp.Models) | || OutputFileName | {InputName}.Generated.fs | Output filename |

Incremental Builds

The task uses content hashing for efficient builds:

IDE Integration

Generated files are created during the build process. Due to how MSBuild evaluates projects, your IDE may not immediately recognize newly generated files.

Recommended: Explicit File Listing

For the best IDE experience, explicitly list generated files in your project:

<ItemGroup>
  <Compile Include="Generated/user.Generated.fs" />
</ItemGroup>

This way, the IDE knows about the file path upfront and will recognize it once generated.

Using Glob Patterns

If you prefer glob patterns:

<ItemGroup>
  <Compile Include="Generated/*.fs" />
</ItemGroup>

Note that after the first build (or after clean), you may need to reload the project in your IDE for it to pick up the new files.

Tips

Troubleshooting

IDE Not Seeing Generated Files

If your IDE doesn't recognize the generated types after building: 1. Try reloading/refreshing the project 2. Switch from glob patterns to explicit file listing 3. Ensure the generated file exists in the Generated/ folder

File Not Updating

dotnet clean && dotnet build

Build Fails After Clean

This is expected on the first build after dotnet clean when using glob patterns. The generated file doesn't exist when MSBuild evaluates <Compile Include="Generated/*.fs" />. Simply run dotnet build again.

Disable Generation

Via project property:

<EnableFabulousAstJsonGeneration>false</EnableFabulousAstJsonGeneration>

Via command line:

dotnet build -p:EnableFabulousAstJsonGeneration=false

Next Steps

val id: x: 'T -> 'T
Multiple items
val int: value: 'T -> int (requires member op_Explicit)

--------------------
type int = int32

--------------------
type int<'Measure> = int
Multiple items
val string: value: 'T -> string

--------------------
type string = System.String
type bool = System.Boolean
type 'T list = List<'T>
type 'T option = Option<'T>

Type something to start searching.