วิธีแยกคุณสมบัติออกจาก Codable ของ Swift 4


112

ใหม่Encodable/ Decodableโปรโตคอลของ Swift 4 ทำให้การจัดลำดับ JSON (de) ค่อนข้างน่าพอใจ อย่างไรก็ตามฉันยังไม่พบวิธีที่จะควบคุมได้อย่างละเอียดว่าคุณสมบัติใดควรเข้ารหัสและควรถอดรหัสคุณสมบัติใด

ฉันสังเกตเห็นว่าการยกเว้นทรัพย์สินจากCodingKeysenum ที่มาพร้อมกันนั้นไม่รวมคุณสมบัติจากกระบวนการทั้งหมด แต่มีวิธีใดที่จะควบคุมได้ละเอียดกว่านี้


คุณกำลังบอกว่าคุณมีกรณีที่คุณมีคุณสมบัติบางอย่างที่คุณต้องการเข้ารหัส แต่คุณสมบัติต่างกันที่คุณต้องการถอดรหัสหรือไม่? (เช่นคุณต้องการให้ประเภทของคุณไม่สามารถหมุนวนได้?) เพราะถ้าคุณสนใจแค่เรื่องการยกเว้นคุณสมบัติการให้ค่าเริ่มต้นและปล่อยให้ไม่อยู่ในCodingKeysenum ก็เพียงพอแล้ว
Itai Ferber

ไม่ว่าคุณจะใช้ข้อกำหนดของCodableโปรโตคอล ( init(from:)และencode(to:)) ด้วยตนเองได้เสมอเพื่อให้สามารถควบคุมกระบวนการได้อย่างสมบูรณ์
Itai Ferber

กรณีใช้งานเฉพาะของฉันคือหลีกเลี่ยงการให้ตัวถอดรหัสควบคุมมากเกินไปซึ่งอาจนำไปสู่การรับ JSON จากระยะไกลจากการเขียนทับค่าคุณสมบัติภายใน วิธีแก้ไขด้านล่างนี้เพียงพอแล้ว!
RamwiseMatt

1
ฉันต้องการเห็นคำตอบ / คุณสมบัติใหม่ของ Swift ที่ต้องจัดการเฉพาะกรณีพิเศษและคีย์ที่ยกเว้นเท่านั้นแทนที่จะนำคุณสมบัติทั้งหมดที่คุณควรได้รับมาใช้ใหม่โดยปกติ
pkamb

คำตอบ:


193

รายการคีย์ในการเข้ารหัส / ถอดรหัสถูกควบคุมโดยประเภทที่เรียกว่าCodingKeys(โปรดสังเกตsที่ส่วนท้าย) คอมไพเลอร์สามารถสังเคราะห์สิ่งนี้ให้คุณได้ แต่สามารถลบล้างสิ่งนั้นได้เสมอ

สมมติว่าคุณต้องการแยกคุณสมบัติnicknameจากการเข้ารหัสและถอดรหัส:

struct Person: Codable {
    var firstName: String
    var lastName: String
    var nickname: String?

    private enum CodingKeys: String, CodingKey {
        case firstName, lastName
    }
}

หากคุณต้องการให้ไม่สมมาตร (เช่นเข้ารหัส แต่ไม่ถอดรหัสหรือในทางกลับกัน) คุณต้องจัดเตรียมการใช้งานencode(with encoder: )และinit(from decoder: ):

struct Person: Codable {
    var firstName: String
    var lastName: String

    // Since fullName is a computed property, it's excluded by default
    var fullName: String {
        return firstName + " " + lastName
    }

    private enum CodingKeys: String, CodingKey {
        case firstName
        case lastName
        case fullName
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(firstName, forKey: .firstName)
        try container.encode(lastName, forKey: .lastName)
        try container.encode(fullName, forKey: .fullName)
    }
}

19
คุณต้องให้nicknameค่าเริ่มต้นเพื่อให้สามารถใช้งานได้ init(from:)มิฉะนั้นมีไม่มีค่าที่สามารถกำหนดสถานที่ให้บริการใน
Itai Ferber

1
@ItaiFerber ฉันเปลี่ยนเป็นอุปกรณ์เสริมซึ่งเดิมอยู่ใน Xcode ของฉัน
Code Different

คุณแน่ใจหรือไม่ว่าต้องระบุencodeในตัวอย่างที่ไม่สมมาตร? เนื่องจากยังคงเป็นพฤติกรรมมาตรฐานฉันจึงไม่คิดว่าจำเป็น เพียงdecodeตั้งแต่ที่ที่ไม่สมส่วนมาจาก
Mark A. Donohoe

1
@MarqueIV ใช่คุณต้อง เนื่องจากfullNameไม่สามารถแมปกับคุณสมบัติที่จัดเก็บไว้คุณต้องจัดเตรียมตัวเข้ารหัสและตัวถอดรหัสที่กำหนดเอง
Code Different

เพิ่งทดสอบสิ่งนี้ใน Swift 5 คุณควรกำหนดค่าคงที่สำหรับคุณสมบัติที่คุณไม่ต้องการถอดรหัสเท่านั้น CodingKeysคุณไม่จำเป็นต้องเพิ่มอย่างชัดเจนกุญแจที่จะ ดังนั้นvar nickname: String { get { "name" } }ควรพอเพียง
ลีโอ

3

หากเราต้องการแยกการถอดรหัสคุณสมบัติสองสามคุณสมบัติออกจากคุณสมบัติชุดใหญ่ในโครงสร้างให้ประกาศเป็นคุณสมบัติทางเลือก รหัสในการคลายตัวเลือกน้อยกว่าการเขียนคีย์จำนวนมากภายใต้ CodingKey enum

ฉันอยากจะแนะนำให้ใช้ส่วนขยายเพื่อเพิ่มคุณสมบัติอินสแตนซ์ที่คำนวณและคุณสมบัติประเภทที่คำนวณ มันแยกคุณสมบัติ comforming codable ออกจากตรรกะอื่น ๆ ด้วยเหตุนี้จึงให้อ่านได้ดีขึ้น


2

อีกวิธีหนึ่งในการแยกคุณสมบัติบางอย่างออกจากตัวเข้ารหัสสามารถใช้คอนเทนเนอร์การเข้ารหัสแยกต่างหากได้

struct Person: Codable {
    let firstName: String
    let lastName: String
    let excludedFromEncoder: String

    private enum CodingKeys: String, CodingKey {
        case firstName
        case lastName
    }
    private enum AdditionalCodingKeys: String, CodingKey {
        case excludedFromEncoder
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let anotherContainer = try decoder.container(keyedBy: AdditionalCodingKeys.self)
        firstName = try container.decode(String.self, forKey: .firstName)
        lastName = try container.decode(String.self, forKey: .lastName)

        excludedFromEncoder = try anotherContainer(String.self, forKey: . excludedFromEncoder)
    }

    // it is not necessary to implement custom encoding
    // func encode(to encoder: Encoder) throws

    // let person = Person(firstName: "fname", lastName: "lname", excludedFromEncoder: "only for decoding")
    // let jsonData = try JSONEncoder().encode(person)
    // let jsonString = String(data: jsonData, encoding: .utf8)
    // jsonString --> {"firstName": "fname", "lastName": "lname"}

}

สามารถใช้แนวทางเดียวกันสำหรับตัวถอดรหัสได้


1

คุณสามารถใช้คุณสมบัติที่คำนวณได้:

struct Person: Codable {
  var firstName: String
  var lastName: String
  var nickname: String?

  var nick: String {
    get {
      nickname ?? ""
    }
  }

  private enum CodingKeys: String, CodingKey {
    case firstName, lastName
  }
}

นี่เป็นเบาะแสสำหรับฉัน - การใช้lazy varคุณสมบัติรันไทม์อย่างมีประสิทธิภาพแยกออกจาก Codable
ChrisH

0

ขณะนี้สามารถทำได้ในท้ายที่สุดมันสิ้นสุดขึ้นเป็นมากunSwiftyและแม้กระทั่งunJSONy ฉันคิดว่าฉันเห็นว่าคุณมาจากไหนแนวคิดของ#ids แพร่หลายใน HTML แต่แทบจะไม่ถูกส่งไปยังโลกJSONที่ฉันคิดว่าเป็นสิ่งที่ดี (TM)

บางCodablestructs จะสามารถที่จะแยกของJSONไฟล์เพียงแค่ปรับถ้าคุณปรับโครงสร้างโดยใช้แฮช recursive เช่นถ้าคุณrecipeเพียงแค่มีอาร์เรย์ของingredientsซึ่งจะมี ingredient_info(อย่างใดอย่างหนึ่งหรือหลาย) วิธีการที่ตัวแยกวิเคราะห์ที่จะช่วยให้คุณปักครอสติเครือข่ายของคุณร่วมกันในสถานที่แรกและคุณจะต้องให้ลิงก์ย้อนกลับบางส่วนผ่านการสำรวจเส้นทางโครงสร้างที่เรียบง่ายหากคุณต้องการเขาจริงๆ เนื่องจากสิ่งนี้ต้องการการปรับโครงสร้างข้อมูลของคุณJSONและของคุณใหม่อย่างละเอียดฉันจึงร่างแนวคิดให้คุณคิด หากคุณเห็นว่าเป็นที่ยอมรับโปรดบอกฉันในความคิดเห็นแล้วฉันสามารถอธิบายเพิ่มเติมได้ แต่ขึ้นอยู่กับสถานการณ์คุณอาจไม่มีเสรีภาพที่จะเปลี่ยนแปลงอย่างใดอย่างหนึ่ง


0

ฉันใช้โปรโตคอลและส่วนขยายร่วมกับ AssociatedObject เพื่อตั้งค่าและรับอิมเมจ (หรือคุณสมบัติใด ๆ ที่ต้องแยกออกจากคุณสมบัติ Codable)

ด้วยเหตุนี้เราจึงไม่จำเป็นต้องใช้ตัวเข้ารหัสและตัวถอดรหัสของเราเอง

นี่คือรหัสการรักษารหัสที่เกี่ยวข้องเพื่อความง่าย:

protocol SCAttachmentModelProtocol{
    var image:UIImage? {get set}
    var anotherProperty:Int {get set}
}
extension SCAttachmentModelProtocol where Self: SCAttachmentUploadRequestModel{
    var image:UIImage? {
        set{
            //Use associated object property to set it
        }
        get{
            //Use associated object property to get it
        }
    }
}
class SCAttachmentUploadRequestModel : SCAttachmentModelProtocol, Codable{
    var anotherProperty:Int
}

ตอนนี้เมื่อใดก็ตามที่เราต้องการเข้าถึงคุณสมบัติ Image เราสามารถใช้กับวัตถุที่ยืนยันกับโปรโตคอล (SCAttachmentModelProtocol)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.