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: BoolUsage
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
- •Use consistent keys - Define constants for metadata keys to avoid typos
- •Document your schema - Keep track of what keys and types each anchor type expects
- •Handle missing values - Always check for nil when accessing metadata
- •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)