HyperbasisHyperbasisDocs
Home

AnyCodableValue

A type-erased Codable value for storing dynamic metadata.

Overview

AnyCodableValue allows you to store arbitrary JSON-compatible values in anchor metadata without losing type information.

public enum AnyCodableValue: Codable, Equatable {
    case string(String)
    case int(Int)
    case double(Double)
    case bool(Bool)
    case array([AnyCodableValue])
    case dictionary([String: AnyCodableValue])
    case null
}

Creating Values

Explicit case construction

let metadata: [String: AnyCodableValue] = [
    "text": .string("Hello"),
    "count": .int(42),
    "price": .double(19.99),
    "active": .bool(true),
    "tags": .array([.string("urgent"), .string("work")]),
    "nested": .dictionary(["key": .string("value")]),
    "empty": .null
]

Using literal syntax

Thanks to ExpressibleBy protocol conformance, you can use Swift literals directly:

let metadata: [String: AnyCodableValue] = [
    "text": "Hello",           // .string
    "count": 42,               // .int
    "price": 19.99,            // .double
    "active": true,            // .bool
    "empty": nil               // .null (via ExpressibleByNilLiteral)
]
Tip

The literal syntax makes metadata more readable. Use explicit cases when you need to distinguish between .int and .double for the same numeric value.

Extracting Values

Accessor properties

public var stringValue: String?
public var intValue: Int?
public var doubleValue: Double?
public var boolValue: Bool?
public var arrayValue: [AnyCodableValue]?
public var dictionaryValue: [String: AnyCodableValue]?
public var isNull: Bool

Usage

let metadata: [String: AnyCodableValue] = anchor.metadata

if let text = metadata["text"]?.stringValue {
    print(text)  // "Hello"
}

if let count = metadata["count"]?.intValue {
    print(count)  // 42
}

if let tags = metadata["tags"]?.arrayValue {
    for tag in tags {
        if let tagString = tag.stringValue {
            print(tagString)  // "urgent", "work"
        }
    }
}

if metadata["empty"]?.isNull == true {
    print("Value is null")
}

Nested Values

AnyCodableValue supports arbitrarily nested structures:

let complexMetadata: [String: AnyCodableValue] = [
    "user": .dictionary([
        "name": .string("John"),
        "age": .int(30),
        "preferences": .dictionary([
            "theme": .string("dark"),
            "notifications": .bool(true)
        ])
    ]),
    "tags": .array([
        .string("featured"),
        .dictionary(["type": .string("category"), "id": .int(1)])
    ])
]

Accessing nested values

if let user = metadata["user"]?.dictionaryValue,
   let prefs = user["preferences"]?.dictionaryValue,
   let theme = prefs["theme"]?.stringValue {
    print(theme)  // "dark"
}

Codable Conformance

AnyCodableValue encodes to and decodes from standard JSON:

{
  "text": "Hello",
  "count": 42,
  "price": 19.99,
  "active": true,
  "tags": ["urgent", "work"],
  "nested": { "key": "value" },
  "empty": null
}

Usage with HBAnchor

Setting metadata on creation

let anchor = HBAnchor(
    spaceId: space.id,
    transform: matrix,
    metadata: [
        "type": "sticky_note",
        "text": "Remember this!",
        "priority": 1,
        "completed": false
    ]
)

Updating metadata

var anchor = existingAnchor

// Update single value
anchor.updateMetadata(key: "completed", value: .bool(true))

// Remove a key
anchor.updateMetadata(key: "priority", value: nil)

// Replace all metadata
anchor.update(metadata: [
    "type": "note",
    "archived": true
])

Type-safe accessors on HBAnchor

// Convenience methods
let text = anchor.stringMetadata(forKey: "text")    // String?
let count = anchor.intMetadata(forKey: "count")     // Int?
let active = anchor.boolMetadata(forKey: "active")  // Bool?

// Generic accessor
let value = anchor.metadata(forKey: "key")          // AnyCodableValue?

Best Practices

  1. Use consistent keys - Define constants for metadata keys to avoid typos
  2. Document your schema - Keep track of what keys and types each anchor type expects
  3. Handle missing values - Always check for nil when accessing metadata
  4. Avoid deep nesting - Keep metadata structures relatively flat for easier access
// Good: Define metadata keys as constants
enum NoteMetadataKey {
    static let text = "text"
    static let color = "color"
    static let priority = "priority"
}

// Usage
let text = anchor.stringMetadata(forKey: NoteMetadataKey.text)

Next Steps

  • HBAnchor - Using metadata with anchors
  • Patterns - Model extension patterns