【问题标题】:Codable with Generics Swift 5可使用泛型 Swift 5 编码
【发布时间】:2019-07-29 03:37:46
【问题描述】:

我正在为我的 API 调用创建一个通用的帖子正文,我的帖子正文大部分是相同的,只是不同 API 调用的数据参数不同,数据充当不同要求的填充物,低于 JSON 帖子正文。

示例 1:

{
  "timeStampUtc": "2019-07-29T03:29:21.729Z",
  ...
  "geoLocationInfo": {
    "latitude": 0,
    "longitude": 0,
    ...
  },
  "data": {
    "loginIdentity": "string",
    "loginPassword": "string"
  }
}

示例 2:

{
  "timeStampUtc": "2019-07-29T03:29:21.729Z",
  ...
  "geoLocationInfo": {
    "latitude": 0,
    "longitude": 0,
    ...
  },
  "data": {
    "wazId": 0,
    "regionId": 0
  }
}

示例 3:

{
  "timeStampUtc": "2019-07-29T03:29:21.729Z",
  ...
  "geoLocationInfo": {
    "latitude": 0,
    "longitude": 0,
   ...
  },
  "data": {
    "loginIdentity": "string",
    "wazID": 0
  }
}

我正在使用可编码和泛型来克服这个要求,我能够完成前两个场景,但是当数据具有不同类型的值时,我会遇到第三个场景。 以下是示例代码,可以在 Playground 中直接试用

struct PostBody<T : Codable>: Codable
{
    var deviceInfo = ""
    var geoLocationInfo = ""
    var data = Dictionary<String, T>()

    enum CodingKeys: String, CodingKey
    {
        case deviceInfo, geoLocationInfo, data
    }

    init(dataDict : Dictionary<String, T>) {
        self.data = dataDict
    }

    init(from decoder : Decoder) throws {
        let container = try decoder.container(keyedBy : CodingKeys.self)
        deviceInfo = try container.decode(String.self, forKey: .deviceInfo)
        geoLocationInfo = try container.decode(String.self, forKey: .geoLocationInfo)
        data = try container.decode(Dictionary.self, forKey: .data)
    }


    func encode(to encoder : Encoder)
    {
        var container = encoder.container(keyedBy : CodingKeys.self)
        do
        {
            try container.encode(deviceInfo, forKey : .deviceInfo)
            try container.encode(geoLocationInfo, forKey : .geoLocationInfo)
            try container.encode(data, forKey : .data)
        }
        catch
        {
            fatalError("Should never happen")
        }
    }
}


let postBody = PostBody<String>(dataDict : ["1" : "1", "2" : "2"])
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try encoder.encode(postBody)
let encodedDataDict2 = try  encoder.encode(postBody)
print(String(data : encodedDataDict2, encoding : .utf8)!)

let postBody1 = PostBody(dataDict : ["1" : 1, "2" : 2])
let encoder1 = JSONEncoder()
encoder1.outputFormatting = .prettyPrinted
try encoder1.encode(postBody1)
let encodedDataDict3 = try  encoder1.encode(postBody1)
print(String(data : encodedDataDict3, encoding : .utf8)!)

【问题讨论】:

    标签: swift generics codable


    【解决方案1】:

    使用枚举来代替泛型来表示不同的类型。随意添加更多类型

    enum StringOrInt : Codable {
        case string(String), integer(Int)
    
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            do {
                let stringValue = try container.decode(String.self)
                self = .string(stringValue)
            } catch DecodingError.typeMismatch {
                let integerValue = try container.decode(Int.self)
                self = .integer(integerValue)
            }
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .string(let stringValue): try container.encode(stringValue)
            case .integer(let integerValue): try container.encode(integerValue)
            }
        }
    }
    
    struct PostBody: Codable
    {
        let deviceInfo, geoLocationInfo : String
        let data : Dictionary<String, StringOrInt>
    }
    
    
    let postBody = PostBody(deviceInfo: "Foo", geoLocationInfo: "Bar", data : ["loginIdentity" : .string("string"), "wazID" : .integer(0)])
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted
    let encodedDataDict2 = try encoder.encode(postBody)
    print(String(data : encodedDataDict2, encoding : .utf8)!)
    

    【讨论】:

    • 很好的答案有点骇人听闻。
    【解决方案2】:

    您收到错误是因为 Dictionary&lt;String, Any&gt; 不符合 Decodable 并且当您编写时:

    struct PostBody<T : Codable>: Codable
    {
    ...
        var data = Dictionary<String, T>()
    

    您将字典 data 限制为仅用于 Value 类型。

    您应该为每个帖子正文创建符合Decodable 的结构,例如:

    struct OnlyString: Codable {
        var str1: String
        var str2: String
    }
    
    struct OnlyInt: Codable {
        var int1: Int
        var int2: Int
    }
    
    struct StringAndInt: Codable {
        var str: String
        var int: Int
    }
    

    然后你可以像这样声明你的PostBody 结构:

    struct PostBody<Data: Codable>: Codable
    {
        var deviceInfo = ""
        var geoLocationInfo = ""
        var data: Data
    
        enum CodingKeys: String, CodingKey
        {
            case deviceInfo, geoLocationInfo, data
        }
    
        init(data : Data) {
            self.data = data
        }
    
        init(from decoder : Decoder) throws {
            let container = try decoder.container(keyedBy : CodingKeys.self)
            deviceInfo = try container.decode(String.self, forKey: .deviceInfo)
            geoLocationInfo = try container.decode(String.self, forKey: .geoLocationInfo)
            data = try container.decode(Data.self, forKey: .data)
        }
    
    
        func encode(to encoder : Encoder)
        {
            var container = encoder.container(keyedBy : CodingKeys.self)
            do
            {
                try container.encode(deviceInfo, forKey : .deviceInfo)
                try container.encode(geoLocationInfo, forKey : .geoLocationInfo)
                try container.encode(data, forKey : .data)
            }
            catch
            {
                fatalError("Should never happen")
            }
        }
    }
    

    并像这样使用它:

    let postBody = PostBody<OnlyString>(data : OnlyString(str1: "1", str2: "2"))
    let encoder = JSONEncoder()
    encoder.outputFormatting = .prettyPrinted
    try encoder.encode(postBody)
    let encodedDataDict2 = try  encoder.encode(postBody)
    print(String(data : encodedDataDict2, encoding : .utf8)!)
    
    let postBody1 = PostBody(data : OnlyInt(int1: 1, int2: 2))
    let encoder1 = JSONEncoder()
    encoder1.outputFormatting = .prettyPrinted
    try encoder1.encode(postBody1)
    let encodedDataDict3 = try  encoder1.encode(postBody1)
    print(String(data : encodedDataDict3, encoding : .utf8)!)
    
    let postBody2 = PostBody(data : StringAndInt(str: "1", int: 2))
    let encoder2 = JSONEncoder()
    encoder2.outputFormatting = .prettyPrinted
    try encoder2.encode(postBody2)
    let encodedDataDict4 = try  encoder2.encode(postBody2)
    print(String(data : encodedDataDict4, encoding : .utf8)!)
    

    【讨论】:

    • 好吧,这会起作用,但会迫使我为每个 API 调用创建多个结构。
    • 没错,但除非你的 API 不断变化,否则你只需要编写一次
    【解决方案3】:

    每当处理 json 数据时,我建议使用 QuickType,因为这可以让您快速获得想法,或者只为您需要的不同语言生成必要的代码。

    • This 是基于您提供的数据的示例。

    • 有几个选项可供使用,例如在 Class 或 Struct 之间进行切换以及仅使用普通类型。还可以选择生成初始化器和修改器。

    【讨论】:

    • 感谢您的回复,但我不想将我的数据硬编码到一个或多个模型中,这个想法是除了上面的数据字段之外,帖子正文模式将始终相同,我想传递数据字典并为我的所有 API 使用相同的帖子正文
    猜你喜欢
    • 1970-01-01
    • 2021-01-29
    • 2021-04-21
    • 2018-10-05
    • 2020-06-17
    • 1970-01-01
    • 2020-04-09
    • 2021-01-26
    • 1970-01-01
    相关资源
    最近更新 更多