DynamicCodable-SebastianPickl0.2.1

DynamicCodable-SebastianPickl0.2.1

Sebastian Pickl 维护。



DynamicCodable

基于 Codable 的 Swift 属性包装器,用于解码(和编码)在(JSON)数据中定义的类型。

DynamicCodable 通过使用 `@DynamicEncodable`, `@DynamicDecodable` 或它们的组合 `@DynamicCodable` 拦截属性来提供序列化和反序列化属性的方法。

示例:路线

protocol Route: DynamicCodableProtocol {}

struct HomeScreen: Codable {
    @DynamicCodable var `self`: Route
    @DynamicCodable var routes: [Route]
    @DynamicCodable var someRouteDict: [String: Route]

    // Optionals that are missing in the JSON (and are not replaced by `null`)
    // can't be wrapped in `@DynamicCodable`, see "Constraints".
    let someOptionalRoute: DynamicCodable<Route>?
}

HomeScreen 的 JSON 可能如下所示:

{
    "self": {
        "type": "homescreen"
    },
    "routes": [
        {
            "type": "detailscreen",
            "id": "550121C5-3D8F-4AC8-AB14-BDF7E6D11626"
        },
        {
            "type": "detailscreen",
            "id": "697167E5-90EE-4CD2-879D-EAF49064F400"
        }
    ],
    "someRouteDict": {
        "profilescreen": {
            "type": "profilescreen",
            "id": "84874B0F-1F41-4380-B3C6-CC53A0DE5453",
            "tracking": {
                "some_tracking_service": {
                    "some_tracking_property": "some_tracking_value"
                }
            }
        }
    }
}

Route 具有公共字段 type(这也是 DynamicCodableProtocol 唯一的要求),用于识别应该反序列化确实类型的实际类型。要使这可行,类型必须在 DynamicDecodableRegistry 中按其相应的标识符进行注册。

DynamicDecodableRegistry.register(DetailScreenRoute.self, typeIdentifier: DetailScreenRoute.type)
DynamicDecodableRegistry.register(HomeScreenRoute.self, typeIdentifier: HomeScreenRoute.type)
DynamicDecodableRegistry.register(ProfileScreenRoute.self, typeIdentifier: ProfileScreenRoute.type)

要使用 Swift 的 Codable 单独反序列化如上所示的 JSON,可以定义一个包含所有可能的 JSON 字段作为可选属性(定义在单个位置)的 Route struct。在一个模块化的设置中,其中路由目标 / 功能(如详细信息屏幕或主页)被分开在不同的模块中,这可能不是理想的解决方案。另一种可能是不使用模型来处理路由,只需反序列化字典。

通过使用 DynamicCodable,可以利用 Swift 的类型系统来创建具有定义单独可选和不可选属性的类型的干净接口。在可能的路线设置中,如以下 DetailScreenRouteHandler,可以访问 DetailScreenRouteid 属性,而无需解包可选值,只获取所需的信息。

protocol RouteHandler {
    associatedtype ConcreteRoute: Route
    associatedtype Content: View
    func view(for route: ConcreteRoute) -> Content
}

protocol Router {
    func callAsFunction(_ route: Route)
    func register<Handler: RouteHandler>(_ handler: Handler)
}

// Interface module "HomeScreen"
struct HomeScreenRoute: Route {
    static let type = "homescreen"

    let type: String
}

// Implementation module "HomeScreenImplementation"
struct HomeScreenRouteHandler: RouteHandler {
    func view(for route: HomeScreenRoute) -> Color {
        .red
    }
}

// Interface module "DetailScreen"
struct DetailScreenRoute: Route {
    static let type = "detailscreen"

    let type: String
    let id: UUID
}

// Implementation module "DetailScreenImplementation"
struct DetailScreenRouteHandler: RouteHandler {
    func view(for route: DetailScreenRoute) -> Text {
        Text(route.id)
    }
}

约束

  • 需要在JSON中通过一个字段type来标识类型,这是DynamicCodableProtocol的协议要求。
  • 这些类型标识符必须是唯一的。原因在于将要被解码的属性的抽象类型如果这个属性是一个字典或数组,不能用作“命名空间”,因为在那些情况下,字典的/数组的类型不能通过属性包装器的泛型Value类型确定。
  • 必须将类型注册在DynamicDecodableRegistry中,使用String类型的标识符(在这种情况下与应用JSON中type字段的值匹配以进行解码)进行解码。
  • 不应使用属性包装器来使用可选值,而应通过定义属性类型,例如Optional,如果JSON中的值可以缺失。这是因为自动Decodable生成时,属性被视为非可选值,因为属性包装器本身是一个非可选值。