Turn Codable values into a tree you can inspect, edit, merge, and decode again.
Use @Codec.Box to choose how individual properties are encoded.
Choose Enigmatic when you need to edit or merge a Codable model as a tree, then
decode it again without a JSON round trip. Its reusable property strategies also
work on their own.
| Alternative | When it may fit better |
|---|---|
Native Codable |
You only read or write known models with JSONEncoder and JSONDecoder. Custom Codable code can handle property formats too, without another library or tree. |
| SwiftyJSON | You want JSON getters with defaults, edits, and simple merges. Enigmatic adds more merge rules and direct model/tree conversion. |
| AnyCodable | You only need mixed value types in Codable arrays or dictionaries. |
| PotentCodables | You need dedicated parsers, YAML or CBOR support, or more native value types. Enigmatic has no parser of its own. |
| CodableWrappers | You want property wrappers or key-renaming macros. Enigmatic has no such macros. |
| BetterCodable | You want ready-made defaults or collections that skip invalid values. |
In our local plain JSON tests, native tools used less time and memory.
PotentCodables' own parser was much faster than decoding Enigma through
Foundation's JSONDecoder. With that same decoder, Enigma and PotentCodables'
AnyValue took similar time. Performance depends on what you do.
Requires Swift 6.1 or newer. Add
SwiftUtility/Enigmatic in Xcode and
link the Enigmatic product to your target. For SwiftPM targets, use
.product(name: "Enigmatic", package: "Enigmatic").
The generic Unix date strategies require Swift 6.2.3 or newer; use the named seconds/milliseconds strategies with older compilers. Some APIs also require newer Apple OS versions; see the compatibility notes.
import Enigmatic
struct User: Codable {
var name: String
var scores: [Int]
}
var tree = try Enigma(encode: User(name: "Ada", scores: [10]))
tree["name"] = "Grace"
tree["scores", 1] = 20
let user = try tree.decode(User.self) // Grace, [10, 20]String keys and array indices form a path. Reading a missing value returns nil.
Assigning nil deletes a child; .null stores a null value. Writing at an array's
count appends a value. An index outside 0...count leaves the tree unchanged.
let original: Enigma = ["settings": ["enabled": false, "retries": 2]]
let patch: Enigma = ["settings": ["enabled": true]]
let merged = original.merging(patch, policy: .replace)
// ["settings": ["enabled": true, "retries": 2]]Nested dictionaries are merged too. .replace takes incoming values; .skip
keeps existing ones. For arrays, you can merge elements by index, append, or
prepend.
import Foundation
import Enigmatic
struct Payload: Codable {
@Codec.Box<Codec.Base64Data> var bytes: Data
@Codec.Box<Codec.StringURL?> var link: URL?
}
let payload = Payload(bytes: Data([1, 2, 3]), link: nil)
let tree = try Enigma(encode: payload) // ["bytes": "AQID"]
let restored = try tree.decode(Payload.self)Strategies also work with arrays, dictionaries, optionals, and sets. An optional
Codec.Box leaves out the key when its value is nil. Missing or null keys
decode as nil. Codec.Either tries Right before Left. Codec.Each combines
models that encode as dictionaries with no overlapping keys.
Warning: When you encode an Enigma tree, Foundation's date and data settings do not choose how those values are written. Use
Codec.Boxon the model to choose the format before building the tree.
DataandDatekeep their original types in memory. Encoding a tree bypassesJSONEncoder.dataEncodingStrategy,JSONEncoder.dateEncodingStrategy, and the usual date/data handling inPropertyListEncoder. Data becomes a byte array; Date becomes seconds since 2001-01-01. Use@Codec.Box<Codec.Base64Data>for Base64 and@Codec.Box<Codec.PlistDate>for seconds since 2001-01-01, orCodec.UnixSecondsDatefor Unix seconds. With Swift 6.2.3 or newer, preferCodec.UnixDate<0>on supported OS versions.FloatandDoublecan storeNaNand positive or negative infinity in memory. These values are not valid JSON numbers. Default JSON encoding andasJsonObjectreject them. When encoding or decoding the tree with your ownJSONEncoderandJSONDecoder, you can use matchingnonConformingFloatEncodingStrategyandnonConformingFloatDecodingStrategysettings. Enigma's model/tree conversions do not have these settings. Use a customCodec.Boxstrategy to store these values as strings.
See explicit serialization strategies
for a complete Data/Date/Float/Double example. Codec.CanonicalFloat changes how
finite floats are stored; it does not make NaN or infinity valid JSON.
API reference · Tree operations · Coding strategies · Serialization · Development
Apache 2.0. See LICENSE.