Encode/Decode Array of Types conforming to protocol with JSONEncoder

前端 未结 4 936
清歌不尽
清歌不尽 2020-11-28 22:52

I\'m trying to find the best way to Encode/Decode an array of structs conforming to a swift protocol using the new JSONDecoder/Encoder in Swift 4.

I made up a little

4条回答
  •  一生所求
    2020-11-28 23:17

    Drawn from the accepted answer, I ended up with the following code that can be pasted into an Xcode Playground. I used this base to add a codable protocol to my app.

    The output looks like this, without the nesting mentioned in the accepted answer.

    ORIGINAL:
    ▿ __lldb_expr_33.Parent
      - title: "Parent Struct"
      ▿ items: 2 elements
        ▿ __lldb_expr_33.NumberItem
          - commonProtocolString: "common string from protocol"
          - numberUniqueToThisStruct: 42
        ▿ __lldb_expr_33.StringItem
          - commonProtocolString: "protocol member string"
          - stringUniqueToThisStruct: "a random string"
    
    ENCODED TO JSON:
    {
      "title" : "Parent Struct",
      "items" : [
        {
          "type" : "numberItem",
          "numberUniqueToThisStruct" : 42,
          "commonProtocolString" : "common string from protocol"
        },
        {
          "type" : "stringItem",
          "stringUniqueToThisStruct" : "a random string",
          "commonProtocolString" : "protocol member string"
        }
      ]
    }
    
    DECODED FROM JSON:
    ▿ __lldb_expr_33.Parent
      - title: "Parent Struct"
      ▿ items: 2 elements
        ▿ __lldb_expr_33.NumberItem
          - commonProtocolString: "common string from protocol"
          - numberUniqueToThisStruct: 42
        ▿ __lldb_expr_33.StringItem
          - commonProtocolString: "protocol member string"
          - stringUniqueToThisStruct: "a random string"
    

    Paste into your Xcode project or Playground and customize to your liking:

    import Foundation
    
    struct Parent: Codable {
        let title: String
        let items: [Item]
    
        init(title: String, items: [Item]) {
            self.title = title
            self.items = items
        }
    
        enum CodingKeys: String, CodingKey {
            case title
            case items
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
    
            try container.encode(title, forKey: .title)
            try container.encode(items.map({ AnyItem($0) }), forKey: .items)
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
    
            title = try container.decode(String.self, forKey: .title)
            items = try container.decode([AnyItem].self, forKey: .items).map { $0.item }
        }
    
    }
    
    protocol Item: Codable {
        static var type: ItemType { get }
    
        var commonProtocolString: String { get }
    }
    
    enum ItemType: String, Codable {
    
        case numberItem
        case stringItem
    
        var metatype: Item.Type {
            switch self {
            case .numberItem: return NumberItem.self
            case .stringItem: return StringItem.self
            }
        }
    }
    
    struct NumberItem: Item {
        static var type = ItemType.numberItem
    
        let commonProtocolString = "common string from protocol"
        let numberUniqueToThisStruct = 42
    }
    
    struct StringItem: Item {
        static var type = ItemType.stringItem
    
        let commonProtocolString = "protocol member string"
        let stringUniqueToThisStruct = "a random string"
    }
    
    struct AnyItem: Codable {
    
        var item: Item
    
        init(_ item: Item) {
            self.item = item
        }
    
        private enum CodingKeys : CodingKey {
            case type
            case item
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
    
            try container.encode(type(of: item).type, forKey: .type)
            try item.encode(to: encoder)
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
    
            let type = try container.decode(ItemType.self, forKey: .type)
            self.item = try type.metatype.init(from: decoder)
        }
    
    }
    
    func testCodableProtocol() {
        var items = [Item]()
        items.append(NumberItem())
        items.append(StringItem())
        let parent = Parent(title: "Parent Struct", items: items)
    
        print("ORIGINAL:")
        dump(parent)
        print("")
    
        let jsonEncoder = JSONEncoder()
        jsonEncoder.outputFormatting = .prettyPrinted
        let jsonData = try! jsonEncoder.encode(parent)
        let jsonString = String(data: jsonData, encoding: .utf8)!
        print("ENCODED TO JSON:")
        print(jsonString)
        print("")
    
        let jsonDecoder = JSONDecoder()
        let decoded = try! jsonDecoder.decode(type(of: parent), from: jsonData)
        print("DECODED FROM JSON:")
        dump(decoded)
        print("")
    }
    testCodableProtocol()
    

提交回复
热议问题