# SurrealValue attributes

Reference for the #[surreal(...)] attributes used with the SurrealValue derive, covering renaming, defaults, flattening and enum tagging.

These attributes customise the `SurrealValue` derive, which is covered in [Working with types](/docs/reference/rust/concepts/working-with-types.md) along with the `kind!` macro and the `Value` helpers.

The `SurrealValue` derive uses its own `#[surreal(...)]` attribute that is inspired by [Serde](https://serde.rs/attributes.html) (familiar names, similar enum tagging ideas), but it is not Serde and not an inheritance of `#[serde(...)]` attributes.

Conversion runs through `SurrealValue` and its derive macro rather than Serde's `Serialize` and `Deserialize`. Where Serde would be reached for to customise field names or flattening, use the matching `#[surreal(...)]` form below.

## Relationship to Serde

| Idea | Serde | `SurrealValue` (`#[surreal(...)]`) |
| --- | --- | --- |
| Rename one field / variant | `rename = "..."` | `rename = "..."` |
| Rename all fields / variants | `rename_all = "..."` | `rename_all = "..."` (same case strings as Serde) |
| Flatten nested object | `flatten` | `flatten` |
| Enum tagging | `tag` / `content` / `untagged` | `tag` / `content` / `untagged` |
| Missing field default | `default` / `default = "path"` | `default` / `default = "path"` (also on the container) |
| Catch-all unit variant | `other` | `other` |
| Skip a field | `skip`, `skip_serializing`, `skip_serializing_if` | Not supported on ordinary fields |
| Conditionally omit enum payload | (use field `skip_serializing_if`) | `skip_content` / `skip_content_if = "..."` on tagged enums |
| Serde-only types | (n/a) | `wrap` for `Serialize + Deserialize` types that do not implement `SurrealValue` |
| Tuple struct as array | (n/a) | `tuple` |
| Literal substitute for a unit | (n/a) | `value = ...` |

Supported `rename_all` values match Serde’s usual set (`lowercase`, `UPPERCASE`, `PascalCase`, `camelCase`, `snake_case`, `SCREAMING_SNAKE_CASE`, `kebab-case`, `SCREAMING-KEBAB-CASE`). An explicit `rename` on a field or variant wins over the container `rename_all`.

`#[surreal(uppercase)]` and `#[surreal(lowercase)]` on enums are legacy aliases for `rename_all = "UPPERCASE"` and `rename_all = "lowercase"`. Prefer `rename_all` in new code. Do not combine them with `rename_all` on the same enum.

The attributes below are the ones the derive currently recognises.

## `surreal(default)`

The `surreal(default)` attribute fills in values when fields are missing during deserialisation. On a struct container, missing fields come from the type’s `Default` implementation. On a single field, use `#[surreal(default)]` for `<T as Default>::default()`, or `#[surreal(default = "path")]` for a custom function path.

```rust
use surrealdb::engine::any::connect;
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
struct UserData {
    num: i32,
    other_num: i32,
}

#[derive(SurrealValue)]
#[surreal(default)]
struct UserDataDefault {
    num: i32,
    other_num: i32,
}

impl Default for UserDataDefault {
    fn default() -> Self {
        UserDataDefault {
            num: 10,
            other_num: 20,
        }
    }
}

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("ns").use_db("db").await.unwrap();

    let mut has_two_fields = db
        .query("CREATE user SET num = 10, other_num = 20")
        .await
        .unwrap();

    let mut has_one_field = db.query("CREATE user SET num = 5").await.unwrap();

    println!(
        "Regular deserialization from DB result: {}",
        has_two_fields
            .take::<Option<UserData>>(0)
            .unwrap()
            .unwrap()
            .into_value()
            .to_sql()
    );

    println!(
        "Deserialization using DB result plus default value: {}",
        has_one_field
            .take::<Option<UserDataDefault>>(0)
            .unwrap()
            .unwrap()
            .into_value()
            .to_sql()
    )
}
```

```title="Output"
Regular deserialization from DB result: { num: 10, other_num: 20 }
Deserialization using DB result plus default value: { num: 5, other_num: 20 }
```

## `surreal(rename)`

The `surreal(rename)` attribute is used to provide a different name for a field on the SurrealDB side than the one used in the Rust code.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
struct UserData {
    num: i32,
}

#[derive(SurrealValue)]
struct UserDataRename {
    #[surreal(rename = "user_num")]
    num: i32,
}

fn main() {
    let user_data = UserData { num: 555 };
    let user_data_rename = UserDataRename { num: 555 };

    println!("Before rename: {}", user_data.into_value().to_sql());
    println!("After rename: {}", user_data_rename.into_value().to_sql());
}
```

```title = "Output"
Before rename: { num: 555 }
After rename: { user_num: 555 }
```

## `surreal(rename_all)`

Apply a case transform to every field name on a struct, or every variant name on an enum, unless a field or variant sets its own `rename`.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
#[surreal(rename_all = "camelCase")]
struct UserProfile {
    full_name: String,
    years_old: i64,
}

fn main() {
    let profile = UserProfile {
        full_name: "Ada".into(),
        years_old: 36,
    };
    // { fullName: 'Ada', yearsOld: 36 }
    println!("{}", profile.into_value().to_sql());
}
```

## `surreal(flatten)`

Merge a nested object’s fields into the parent object instead of nesting them under one key. This is the Surreal equivalent of Serde’s `#[serde(flatten)]`.

```rust
use surrealdb_types::{SurrealValue, ToSql, Value};

#[derive(SurrealValue)]
struct Coords {
    x: i64,
    y: i64,
}

#[derive(SurrealValue)]
struct Point {
    name: String,
    #[surreal(flatten)]
    coords: Coords,
}

fn main() {
    let point = Point {
        name: "origin".into(),
        coords: Coords { x: 0, y: 0 },
    };
    // { name: 'origin', x: 0, y: 0 }
    println!("{}", point.into_value().to_sql());
}
```

`flatten` cannot be combined with `rename` on the same field: there is no single key left to rename.

> [!NOTE]
> A struct that flattens a field needs `Value` in scope, as in the import above. The code generated for `flatten` refers to `Value` without qualifying it, so leaving the import out fails to compile with `` cannot find type `Value` in this scope ``, reported against the `SurrealValue` derive rather than any line you wrote.

## `surreal(uppercase)` and `surreal(lowercase)`

These two attributes are legacy aliases for `rename_all = "UPPERCASE"` and `rename_all = "lowercase"` on enums. Prefer `rename_all` when writing new types.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
enum LogLevel {
    Debug(String),
    Info(String),
}

#[derive(SurrealValue)]
#[surreal(uppercase)]
enum LogLevelUpper {
    Debug(String),
    Info(String),
}

#[derive(SurrealValue)]
#[surreal(lowercase)]
enum LogLevelLower {
    Debug(String),
    Info(String),
}

fn main() {
    let log_level = LogLevel::Debug("User1".into());
    let log_level_upper = LogLevelUpper::Debug("User1".into());
    let log_level_lower = LogLevelLower::Debug("User1".into());

    println!("Before attribute: {}", log_level.into_value().to_sql());
    println!("After uppercase: {}", log_level_upper.into_value().to_sql());
    println!("After lowercase: {}", log_level_lower.into_value().to_sql());
}
```

```title = "Output"
Before attribute: { Debug: 'User1' }
After uppercase: { DEBUG: 'User1' }
After lowercase: { debug: 'User1' }
```

## `surreal(tuple)`

As SurrealQL does not have a tuple type, this attribute can be used to interface in which a Rust tuple struct is treated as an array (instead of a single value) and vice versa.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
struct UserData(i32);

#[derive(SurrealValue)]
#[surreal(tuple)]
struct UserDataTuple(i32);

fn main() {
    println!(
        "Without tuple attribute: {}",
        UserData(555).into_value().to_sql()
    );
    println!(
        "With tuple attribute: {}",
        UserDataTuple(555).into_value().to_sql()
    );
}
```

```title="Output"
Without tuple attribute: 555
With tuple attribute: [555]
```

## `surreal(untagged)`

The `surreal(untagged)` attribute removes the tag from the variant of an enum. This is similar to using `VALUE` in SurrealQL to show only the value and not the field name of a record.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
enum LogLevel {
    Debug(String),
    Info(String),
}

#[derive(SurrealValue)]
#[surreal(untagged)]
enum LogLevelUntagged {
    Debug(String),
    Info(String),
}

fn main() {
    let log_level = LogLevel::Debug("User1".into());
    let log_level_untagged = LogLevelUntagged::Debug("User1".into());

    println!("Before untagged: {}", log_level.into_value().to_sql());
    println!(
        "After untagged: {}",
        log_level_untagged.into_value().to_sql()
    );
}
```

```title="Output"
Before untagged: { Debug: 'User1' }
After untagged: 'User1'
```

## `surreal(tag)`

The `surreal(tag)` attribute can be used to give a tag to a variant. This will create a structure in which the new tag value is the field name, and the variant its value.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
enum LogLevel {
    Debug,
    Info,
}

#[derive(SurrealValue)]
#[surreal(tag = "log_level")]
enum LogLevelTag {
    Debug,
    Info,
}

fn main() {
    let log_level = LogLevel::Debug;
    let log_level_tag = LogLevelTag::Debug;
    println!("\n___surreal(tag)___");
    println!("Before tag: {}", log_level.into_value().to_sql());
    println!("After tag: {}", log_level_tag.into_value().to_sql());
}
```

```title="Output"
Before tag: { Debug: {  } }
After tag: { log_level: 'Debug' }
```

## `surreal(content)`

While the `surreal(tag)` attribute on its own can only be used on variants that do not hold data, the `surreal(content)` makes this possible.

```rust
use surrealdb_types::{SurrealValue, ToSql};

#[derive(SurrealValue)]
enum LogLevel {
    Debug(String),
    Info(String),
}

#[derive(SurrealValue)]
#[surreal(tag = "log_level", content = "user")]
enum LogLevelContent {
    Debug(String),
    Info(String),
}

fn main() {
    let log_level = LogLevel::Debug("User1".to_string());
    let log_level_tag = LogLevelContent::Debug("User1".to_string());

    println!("Before content: {}", log_level.into_value().to_sql());
    println!("After content: {}", log_level_tag.into_value().to_sql());
}
```

```title="Output"
Before content: { Debug: 'User1' }
After content: { log_level: 'Debug', user: 'User1' }
```

## `surreal(skip_content)` and `surreal(skip_content_if)`

On enums that use adjacent tagging (`tag` plus `content`), these control whether the content field is written (and whether it may be absent when reading).

They are the closest Surreal analogues to Serde’s `skip_serializing_if`, but they apply to the enum content field, not to arbitrary struct fields.

| Attribute | Effect |
| --- | --- |
| `skip_content` | Never emit the content field for that enum or variant |
| `skip_content_if = "path"` | Emit content only when the predicate returns false (for example `Value::is_empty`) |

```rust
use surrealdb_types::{SurrealValue, ToSql, Value};

#[derive(SurrealValue)]
#[surreal(tag = "kind", content = "details", skip_content_if = "Value::is_empty")]
enum ApiStatus {
    Ok,
    Error { message: String },
}

fn main() {
    // Unit variant: content omitted when empty
    // { kind: 'Ok' }
    println!("{}", ApiStatus::Ok.into_value().to_sql());

    // Named variant: content present when there is data
    // { kind: 'Error', details: { message: 'boom' } }
    println!(
        "{}",
        ApiStatus::Error {
            message: "boom".into()
        }
        .into_value()
        .to_sql()
    );
}
```

You can also put `skip_content` or `skip_content_if` on individual variants. They only apply to enums that already declare a `tag` (with or without `content`).

## `surreal(other)`

On a unit variant, `other` marks a deserialisation catch-all: if no other variant matches, that variant is chosen instead of returning an error. At most one variant per enum should use it. It cannot be combined with `rename` or `value` on the same variant.

```rust
use surrealdb_types::SurrealValue;

#[derive(Debug, PartialEq, SurrealValue)]
#[surreal(untagged)]
enum WireFlag {
    #[surreal(value = true)]
    On,
    #[surreal(value = false)]
    Off,
    #[surreal(other)]
    Unknown,
}
```

## `surreal(value)`

This attribute can be used on the fields of an enum marked with `surreal(untagged)` to give it a substitute value. The value that follows this attribute can be a NONE, NULL, bool, string, int, or float.

```rust
use surrealdb_types::{SurrealValue, ToSql};

fn main() {
    #[derive(Clone, Debug, SurrealValue)]
    #[surreal(untagged)]
    pub enum LogLevel {
        Regular,
        Verbose,
        Off,
    }

    #[derive(Clone, Debug, SurrealValue)]
    #[surreal(untagged)]
    pub enum LogLevelValue {
        #[surreal(value = "info")]
        Regular,
        #[surreal(value = "debug")]
        Verbose,
        #[surreal(value = NONE)]
        Off,
    }

    println!("Only untagged: {}", LogLevel::Off.into_value().to_sql());
    println!(
        "Untagged plus value: {}",
        LogLevelValue::Off.into_value().to_sql()
    );
}
```

```title="Output"
With only untagged: 'Off'
With untagged plus substitute value: NONE
```

## `surreal(wrap)`

This attribute can be used on fields with a type that implements serde's `Serialize` and `Deserialize` traits, but not `SurrealValue`.

This is meant for interoperability and should only be used if necessary.

```rust
use surrealdb_types::{SurrealValue, ToSql};
use serde::{Serialize, Deserialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExternStruct {
    foo: String,
    bar: String,
}

#[derive(Clone, Debug, SurrealValue)]
pub struct OurStruct {
    baz: String,
    #[surreal(wrap)]
    external: ExternStruct
}
```

## More examples

Here are some more examples from the SurrealDB source code showing how the `surreal` attribute can be used.

```rust
use surrealdb_types::{SurrealValue, Value};

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(untagged)]
enum EnumMixedWithValue {
    #[surreal(value = false)]
    None,
    Some(Vec<String>),
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag", content = "content")]
enum EnumTaggedWithTagAndContent {
    Foo,
    Bar { prop: String },
    Baz(String),
    Qux(String, i64),
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag", content = "content", lowercase)]
enum EnumTaggedWithTagAndContentLowercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag", content = "content", uppercase)]
enum EnumTaggedWithTagAndContentUppercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag")]
enum EnumTaggedWithTag {
    Foo,
    Bar { prop: String },
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag", lowercase)]
enum EnumTaggedWithTagLowercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tag = "tag", uppercase)]
enum EnumTaggedWithTagUppercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
enum EnumTaggedVariant {
    Foo,
    Bar { prop: String },
    Baz(String),
    Qux(String, i64),
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(lowercase)]
enum EnumTaggedVariantLowercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(uppercase)]
enum EnumTaggedVariantUppercase {
    Foo,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(untagged)]
enum EnumUnitValue {
    #[surreal(value = true)]
    True,
    #[surreal(value = false)]
    False,
    #[surreal(value = null)]
    Null,
    #[surreal(value = none)]
    None,
    #[surreal(value = "Hello")]
    String,
    #[surreal(value = 123)]
    Int,
    #[surreal(value = 123.45)]
    Float,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(untagged)]
enum EnumUntagged {
    Foo,
    Bar,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(untagged, lowercase)]
enum EnumUntaggedLowercase {
    Foo,
    Bar,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(untagged, uppercase)]
enum EnumUntaggedUppercase {
    Foo,
    Bar,
}

#[derive(SurrealValue, Debug, PartialEq)]
struct PersonRenamed {
    #[surreal(rename = "full_name")]
    name: String,
    #[surreal(rename = "years_old")]
    age: i64,
}

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(tuple)]
struct StringWrapperTuple(String);

#[derive(SurrealValue, Debug, PartialEq)]
#[surreal(value = true)]
struct UnitStructWithValue;

#[derive(Clone, Debug, SurrealValue, PartialEq)]
#[surreal(default)]
struct TestDefault {
    str: String,
    boolean: bool,
    optional: Option<String>,
}

impl Default for TestDefault {
    fn default() -> Self {
        TestDefault {
            str: "default".to_string(),
            boolean: true,
            optional: None,
        }
    }
}

fn main() {}
```
