Swift Codable 记录解析路径

在我们的工作中,各种特殊情况都有可能遇到,某些特定情况下,需要我们记录模型的解析路径,例如:

{
    "owner":{
        "name":"zhy",
        "age":18
    },
    "visitor":{
        "name":"weixian",
        "age":18
    }
}

对应的模型如下:

struct Role: Codable {
    var owner: User
    var visitor: User
}

struct User: Codable {
    var name: String
    var age: Int
}
1、我们想要知道 Role的属性的名字的字符串,这个时候利用 Codable 中的Decoder协议提供的 API 来获取解析路径:
/// A type that can decode values from a native format into in-memory
/// representations.
public protocol Decoder {

    /// The path of coding keys taken to get to this point in decoding.
    var codingPath: [CodingKey] { get }
    ...
}

codingPath 的注释为: 在解码中达到这一点所采用的编码 Key 路径。

所以我们重写User的解析过程,并新增属性存储解析路径:

struct User: Codable {
    var name: String
    var age: Int
    var path: String
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.age = try container.decode(Int.self, forKey: .age)
        self.path = decoder.codingPath.last?.stringValue ?? ""
    }
}

此时,我们拿到解析结果 owner.path 即为 owner

2. 我们想要知道 name 的属性名字的字符串,同时获取对应角色名 即想知道其是 owner.name 还是 visitor.name

我们仍准备采用上面的方法,但是我没找到直接重写 Int, String 解析过程的方法,就算是找到了,好像也太麻烦了,同时还需要手动添加 path属性, 有简单的方法吗?

有,利用属性包装 可以帮我们简化调用,利用 OC runtime 的属性关联值以及Swift 的协议默认实现可以帮我们新增属性:

我们扩展DeCodable 协议,要求所有遵守此协议的对象持有 codablePath 属性和 codablePathName 属性,并且提供默认实现:

struct ZYAssociateKeys {
    static var ZYCodablePath: String = "ZYCodablePath"
    static var ZYCodablePathName: String = "ZYCodablePathName"
    
}

public extension Decodable {
    var codablePath: String {
        get { return objc_getAssociatedObject(self, &ZYAssociateKeys.ZYCodablePath) as? String  ?? ""}
        set { objc_setAssociatedObject(self, &ZYAssociateKeys.ZYCodablePath, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) }
    }
    
    var codablePathName: String {
        get { return objc_getAssociatedObject(self, &ZYAssociateKeys.ZYCodablePathName) as? String  ?? ""}
        set { objc_setAssociatedObject(self, &ZYAssociateKeys.ZYCodablePathName, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) }
    }
}

此时不管是 Role 还是 User 亦或者是 String, Int, Double 之类都将自动获得这两个属性

下面我们实现一个 Path 属性包装器:

@propertyWrapper struct Path: Codable {
    var wrappedValue: Value
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        wrappedValue = try container.decode(Value.self)

        decoder.codingPath.forEach {
            if !wrappedValue.codablePath.isEmpty {
                wrappedValue.codablePath.append(".")
            }
            wrappedValue.codablePath.append($0.stringValue)
        }
        wrappedValue.codablePathName = decoder.codingPath.last?.stringValue ?? ""
    }
}

typealias path = Path

此时我们的模型写成:

struct Role: Codable {
    @path var owner: User
    @path var visitor: User
}

struct User: Codable {
    @path var name: String
    @path var age: Int
}

我们获取一下结果看是否符合预期:

let model = try? JSONDecoder().decode(Role.self, from: json.data(using: .utf8)!)

let oP = model?.owner.codablePath
let opN = model?.owner.codablePathName
let namePath = model?.owner.name.codablePath
let namePahtName = model?.owner.name.codablePathName

print(oP)
print(opN)
print(namePath)
print(namePahtName)

/* ---- log ---- */
Optional("")
Optional("")
Optional("owner.name")
Optional("name")

很遗憾,并没有达到我们的预期,这是因为swift 中 struct 是值类型,无法利用关联值给其新增属性,所以只能改成:

class User: Codable {
    @path var name: String
    @path var age: Int
}

这个是时候变一切都按预期运行了~

你可能感兴趣的:(Swift Codable 记录解析路径)