# csharp-rs
[](https://crates.io/crates/csharp-rs)
[](https://docs.rs/csharp-rs)
[](https://github.com/Pulsar-Anvil-Studios/csharp-rs/actions/workflows/rust-checks.yml)
[](LICENSE)
[](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0.html)
Generate C# type definitions from Rust structs and enums via `#[derive(CSharp)]`.
Heavily inspired by the excellent [ts-rs](https://github.com/Aleph-Alpha/ts-rs) crate.
## Get Started
Add the derive macro to your Rust types:
```rust
use csharp_rs::CSharp;
#[derive(CSharp)]
#[csharp(export)]
struct Player {
name: String,
level: i32,
active: bool,
inventory: Vec<String>,
}
```
Run `cargo test` and get a generated `.cs` file:
```csharp
// <auto-generated/>
using System;
using System.Text.Json.Serialization;
namespace Generated;
public sealed record Player
{
[JsonPropertyName("name")]
public string Name { get; init; }
[JsonPropertyName("level")]
public int Level { get; init; }
[JsonPropertyName("active")]
public bool Active { get; init; }
[JsonPropertyName("inventory")]
public List<string> Inventory { get; init; }
}
```
## About
This crate was entirely vibe-coded as a side project.
It was born from a Unity game that needed to share types with a Rust HTTP backend,
and the lack of a Rust-to-C# type generation tool at the time.
## ✨ Features
- **Derive macro** on structs and enums — `#[derive(CSharp)]`
- **Serde attribute support** — `rename_all`, `rename`, `skip`, `skip_serializing`, `skip_serializing_if`, `flatten`, and all 4 tagged enum representations
- **Dual serializer** — `System.Text.Json` (default) and `Newtonsoft.Json`
- **C# version targeting** — Unity, C# 9, 10, 11, 12
- **Tagged enums** — auto-generated `JsonConverter<T>` for all serde tagging modes, with native `[JsonPolymorphic]` support for C# 11+
- **Optional type integrations** — `chrono`, `uuid`, `serde_json` via feature flags
- **Export at test time** — `#[csharp(export)]` generates `.cs` files when running `cargo test`
- **Runtime configuration** — `Config` builder pattern with environment variable support
## 📦 Installation
```bash
cargo add csharp-rs
```
### Optional features
| `chrono-impl` | `NaiveDate` → `DateOnly`, `DateTime<Utc>` → `DateTimeOffset`, `NaiveTime` → `TimeOnly`, `Duration` → `TimeSpan` |
| `uuid-impl` | `Uuid` → `Guid` |
| `serde-json-impl` | `serde_json::Value` → `JsonElement` (STJ) / `JToken` (Newtonsoft) |
```bash
cargo add csharp-rs --features chrono-impl,uuid-impl,serde-json-impl
```
## 🚀 Quick Start
### 1. Derive and annotate your types
```rust
use csharp_rs::CSharp;
#[derive(CSharp)]
#[csharp(export)] // export to default directory
#[csharp(namespace = "MyGame.Shared")] // override default namespace
struct GameState {
round: u32,
players: Vec<String>,
winner: Option<String>,
}
```
### 2. Configure the output
Use the builder pattern:
```rust
use csharp_rs::{Config, Serializer, CSharpVersion};
let cfg = Config::default()
.with_serializer(Serializer::Newtonsoft)
.with_target(CSharpVersion::CSharp11)
.with_namespace("MyGame.Shared")
.with_export_dir("./generated");
```
Or set environment variables for CI:
```bash
CSHARP_RS_SERIALIZER=newtonsoft # "stj" or "newtonsoft"
CSHARP_RS_TARGET=11 # "unity", "9", "10", "11", "12"
CSHARP_RS_NAMESPACE=MyGame.Shared
CSHARP_RS_EXPORT_DIR=./generated
```
> `#[csharp(namespace = "...")]` on a type overrides the global `Config` namespace.
### 3. Generate
```bash
cargo test
```
C# files are written to the configured export directory (default: `./csharp-bindings`).
## 🔧 Serde Attributes
| `#[serde(rename_all = "...")]` | Apply naming convention to all fields/variants |
| `#[serde(rename = "...")]` | Rename an individual field or variant |
| `#[serde(skip)]` | Omit field/variant from C# output |
| `#[serde(skip_serializing)]` | Include field as output-only |
| `#[serde(skip_serializing_if = "...")]` | Conditional serialization |
| `#[serde(flatten)]` | Inline nested struct fields or `HashMap` as extension data |
| `#[serde(tag = "...")]` | Internally tagged enum |
| `#[serde(tag = "...", content = "...")]` | Adjacently tagged enum |
| `#[serde(untagged)]` | Untagged enum |
## 🔄 Serializer Comparison
The same Rust struct generates different C# attributes depending on the serializer:
**System.Text.Json** (default):
```csharp
using System.Text.Json.Serialization;
public sealed record Player
{
[JsonPropertyName("name")]
public string Name { get; init; }
}
```
**Newtonsoft.Json**:
```csharp
using Newtonsoft.Json;
public sealed record Player
{
[JsonProperty("name")]
public string Name { get; init; }
}
```
> Unity mode generates `sealed class` with `{ get; set; }` properties instead of records.
## 🎯 C# Version Targeting
| Unity | No | No | No | No |
| C# 9 | Yes | No | No | No |
| C# 10 | Yes | Yes | No | No |
| C# 11 | Yes | Yes | Yes | Yes (STJ only) |
| C# 12 | Yes | Yes | Yes | Yes (STJ only) |
> Unity mode uses `sealed class` with `{ get; set; }` properties instead of records.
## 🏷️ Enums
### Simple enums
```rust
#[derive(CSharp)]
enum Color {
Red,
Green,
Blue,
}
```
```csharp
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Color
{
Red,
Green,
Blue,
}
```
### Tagged enums
```rust
#[derive(CSharp)]
#[serde(tag = "type")]
enum Message {
Request { id: String, method: String },
Quit,
}
```
```csharp
[JsonConverter(typeof(MessageConverter))]
public abstract record Message;
public sealed record Request(
[property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("method")] string Method
) : Message;
public sealed record Quit : Message;
// + auto-generated MessageConverter class
```
All four serde tagging modes are supported: internally tagged, adjacently tagged, externally tagged, and untagged.
## ⚠️ Limitations
- **Tuple variants** in tagged enums are rejected with a compile error
- **Tuple structs** are not supported (only named-field structs)
- **Generic Rust types** with type parameters are not yet supported
## 🙏 Acknowledgements
This project is heavily inspired by [ts-rs](https://github.com/Aleph-Alpha/ts-rs), which generates TypeScript type definitions from Rust types.
csharp-rs follows the same derive-and-export pattern and draws from ts-rs's runtime `Config` design (introduced in ts-rs v12).
## 📄 License
MIT — see [LICENSE](LICENSE) for details.