Skip to content

Instantly share code, notes, and snippets.

@marshmallowrobot
Last active January 12, 2026 18:19
Show Gist options
  • Select an option

  • Save marshmallowrobot/f98a1bc8a4aa8070c0dc133011af0dc7 to your computer and use it in GitHub Desktop.

Select an option

Save marshmallowrobot/f98a1bc8a4aa8070c0dc133011af0dc7 to your computer and use it in GitHub Desktop.
In Swift, use AnyCodable to represent dynamic JSON-schemas. This type is Codable, Equatable, and Hashable. It is also Firestore-friendly (works with NSNumber).
import FirebaseFirestore
/// A Codable representation of any JSON-compatible value
enum AnyCodable: Codable, Equatable, Hashable {
case null
case bool(Bool)
case int(Int)
case double(Double)
case string(String)
case array([AnyCodable])
case dictionary([String: AnyCodable])
// MARK: - Any initializer (Firestore / Foundation)
init?(_ value: Any) {
switch value {
case is NSNull:
self = .null
case let v as Bool:
self = .bool(v)
case let v as Int:
self = .int(v)
case let v as Int64:
self = .int(Int(v))
case let v as Double:
self = .double(v)
case let v as String:
self = .string(v)
case let v as [Any]:
self = .array(v.compactMap { AnyCodable($0) })
case let v as [String: Any]:
self = .dictionary(
v.compactMapValues { AnyCodable($0) }
)
case let v as NSNumber:
if CFGetTypeID(v) == CFBooleanGetTypeID() {
self = .bool(v.boolValue)
} else if v.doubleValue.truncatingRemainder(dividingBy: 1) == 0 {
self = .int(v.intValue)
} else {
self = .double(v.doubleValue)
}
default:
return nil
}
}
// MARK: - Codable
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .null
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if let int = try? container.decode(Int.self) {
self = .int(int)
} else if let double = try? container.decode(Double.self) {
self = .double(double)
} else if let string = try? container.decode(String.self) {
self = .string(string)
} else if let array = try? container.decode([AnyCodable].self) {
self = .array(array)
} else if let dict = try? container.decode([String: AnyCodable].self) {
self = .dictionary(dict)
} else {
throw DecodingError.typeMismatch(
AnyCodable.self,
.init(
codingPath: decoder.codingPath,
debugDescription: "Unsupported JSON value"
)
)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .null:
try container.encodeNil()
case .bool(let v):
try container.encode(v)
case .int(let v):
try container.encode(v)
case .double(let v):
try container.encode(v)
case .string(let v):
try container.encode(v)
case .array(let v):
try container.encode(v)
case .dictionary(let v):
try container.encode(v)
}
}
// MARK: - Underlying value
var value: Any {
switch self {
case .null:
return NSNull()
case .bool(let v):
return v
case .int(let v):
return v
case .double(let v):
return v
case .string(let v):
return v
case .array(let v):
return v.map(\.value)
case .dictionary(let v):
return v.mapValues(\.value)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment