MetaCodable

repository·main·Indexed 21 days ago

https://github.com/swiftylab/metacodable

A Swift framework that uses macros to automate and enhance Codable implementations. It provides tools for complex key mapping via @CodedAt and @CodedIn, default values with @Default, and custom decoding strategies through HelperCoders. MetaCodable supports advanced enum tagging (internal and adjacent), handles nested JSON structures, and includes a build tool plugin, MetaProtocolCodable, for extended configuration.

Tokens
3.1K
Snippets
10
Records
19
Agent score
68%

What's inside MetaCodable

  1. Overview of MetaCodable features

    main

    MetaCodable is a framework that uses Swift macros to supercharge Codable implementations. It provides several advanced capabilities:

    • Custom Key Mapping: Use CodedAt(_:) for single keys, or CodedIn(_:) to flatten nested keys.
    • Composition: Create compositions of multiple Codable types using CodedAt(_:) with no arguments.
    • Fallback Values: Provide defaults for missing values or decoding errors using Default(_:), Default(ifMissing:), or Default(ifMissing:forErrors:).
    • Custom Strategies: Use HelperCoder (e.g., LossySequenceCoder) with CodedBy(_:) to define custom encoding/decoding logic.
    • Enum Handling: Support enums with custom case identifiers via CodedAs(_:_:)-8wdaz or handle untagged enums with UnTagged().
    • Property Control: Ignore properties during encoding/decoding with IgnoreCoding(), IgnoreDecoding(), or IgnoreEncoding(). You can also ignore all initialized properties using IgnoreCodingInitialized().
    • Global Strategies: Apply common strategies to all properties of a type using the commonStrategies parameter in the Codable(commonStrategies:) macro.
  2. Overview of HelperCoders capabilities

    main

    HelperCoders provides a collection of helpers designed to level up MetaCodable's generated implementations by reducing boilerplate for common encoding/decoding tasks.

    Key capabilities include:

    • Basic Data: Decoding types like Bool, Int, or String from other basic types.
    • Date Handling: Custom approaches such as UNIX timestamps or text-formatted dates.
    • Data Handling: Converting between Data and formats like Base64 text.
    • Non-conforming Floats: Handling text-based representations of infinity and NaN.
    • Composition: Using property wrappers or conditional decoding/encoding.
    • Sequences: Specialized coding for sequences.
  3. Represent tagged enums with different JSON formats

    main

    Standard Swift Codable enums typically expect an 'external tagged' format. MetaCodable extends this to support other common JSON patterns for enums, such as:

    1. Internal Tagging: A type field alongside the data.
      { "type": "load", "key": "MyKey" }
    2. Adjacent Tagging: A type field and a content object containing the data.
      { "type": "load", "content": { "key": "MyKey" } }

    This allows a single Swift enum to represent various data variations without complex manual decoding logic.

  4. Use HelperCoders for common decoding tasks

    main

    The library provides several built-in HelperCoder implementations to handle common non-standard data formats:

    • LossySequenceCoder: Decodes only valid elements in a sequence, skipping invalid ones instead of failing the entire array.
    • ValueCoder: Coerces basic types (e.g., decoding an Int from a String like "1" or a Bool from "yes").
    • Since1970DateCoder: Handles UNIX timestamps.
    • DateCoder / ISO8601DateCoder: Handles specific date formats.
    • Base64Coder: Handles Base64 encoded strings.

    You can also implement your own custom logic by conforming to the HelperCoder protocol.

  5. Handling Encodable conformance for actors

    main

    For actor types, Codable(commonStrategies:) automatically generates Decodable conformance, but it does not generate Encodable conformance.

    This is because generating Encodable requires a synchronous encode(to:) method. Making this method nonisolated to allow conformance would prevent the actor from safely accessing its mutable properties.

    Requirement: You must implement Encodable conformance manually for actor types.

  6. Install HelperCoders via Swift Package Manager

    main

    To use HelperCoders in your Swift project, add MetaCodable as a dependency in your Package.swift file, and then specifically include the HelperCoders product in your target's dependencies.

    // In Package.swift
    .package(url: "https://github.com/SwiftyLab/MetaCodable.git", from: "1.0.0"),
    
    // In your target definition
    .target(
        name: "YourTarget",
        dependencies: [
            .product(name: "HelperCoders", package: "MetaCodable")
        ]
    )
  7. Explicitly specify types for @Codable macro expansion

    main

    Because the Swift compiler does not provide type inference data to macros, Codable(commonStrategies:) cannot determine the type of variables that rely on implicit typing. To avoid macro expansion errors, you must explicitly declare the type of all properties in a @Codable struct or class.

    Incorrect:

    @Codable
    struct Model {
        let value = 1
    }

    Correct:

    @Codable
    struct Model {
        let value: Int = 1
    }
  8. Install MetaCodable via Swift Package Manager

    main

    To use MetaCodable in your Swift project, add it as a dependency in your Package.swift file. First, add the package to your dependencies array, then add the MetaCodable product to your specific target's dependencies.

    // Add to dependencies
    .package(url: "https://github.com/SwiftyLab/MetaCodable.git", from: "1.0.0"),
    
    // Add to target dependencies
    .product(name: "MetaCodable", package: "MetaCodable"),
  9. Configure the MetaProtocolCodable build tool plugin

    main

    You can provide additional customization options for the MetaProtocolCodable build tool plugin using a configuration file.

    Supported Formats

    The file can be in either plist or json format.

    Naming Requirements

    The filename must match metacodableconfig after removing non-alphanumeric characters and converting to lowercase. Supported examples include:

    • MetaCodableConfig.plist
    • meta_codable_config.json
    • meta-codable-config.json

    Placement Rules

    • Swift Packages: The file must be added at the target root directory.
    • Xcode Targets: The file must be explicitly added as part of the target.

    Key Sensitivity

    Option names and keys within the file are case-insensitive (e.g., Scan, scan, and SCAN are treated identically).

  10. Customizing enum-case associated values

    main

    Swift does not currently allow macro attributes to be attached to individual enum-case arguments. Therefore, you cannot apply customization attributes (like @CodedAt) directly to arguments within an enum case.

    Workaround: Extract the enum-case arguments into a separate @Codable struct and use that struct as the associated value for the enum case. This allows you to apply customization options within the struct itself.

    Example Workaround:

    @Codable
    enum SomeEnum {
        case string(StringData)
    
        @Codable
        struct StringData {
            @CodedAt("data")
            let data: String
        }
    }
  11. Fixing macro errors with SwiftData classes

    main

    In certain SwiftUI customization scenarios, the Swift compiler may fail to send protocol data to Codable(commonStrategies:). This causes the macro to incorrectly assume a class inherits Codable conformance from a superclass, leading to errors (especially with SwiftData classes).

    Workaround: Use the Inherits(decodable:encodable:) macro to explicitly indicate that the class does not inherit Codable conformance from its superclass.

    // Use this to explicitly state the class does not inherit Codable conformance
    @Inherits(decodable: false, encodable: false)
    class MySwiftDataModel: ... {
        // ...
    }