简体   繁体   中英

Override Codable implementation in struct

Is there a way to override the Codable implementation for a struct?

Code that can't be changed:

struct obj1 : Codable {
   var values:[Int]
   ...
}

Desired JSON:

{ "x": 1, "y": 2, "z": 3 }

This doesn't work:

extension obj1 {
   init(from decoder: Decoder) throws {
      //This is never called
   }

   func encode(to encoder: Encoder) throws {
      //This is never called
   }
}

Backstory:

I'm in the process of porting an existing application to iOS, and converting to Swift from a mixture of C++ and Objective-C. This application uses Metal to do 3D graphics, and therefore makes extensive use of math constructs like Vector, Matrix, Quaternion, etc.

At the beginning of the project I decided to use Apple's simd library for these various types. I gave them a typealias to match the objects we already have to aid porting, and extensions to add various functionality that wasn't built in. This all works great.

I'm now at the point where I need to be able to convert these types to/from JSON, and I've run into a problem. The simd structs already support Codable, but they use a different format than the one I need to support. Since simd uses structs, I can't derive from it and provide my own definition of init(from) and encode . It also appears as though I can't put my own definition in an extension either (the compiler doesn't complain but my versions aren't called either).

I've currently got this working using a struct wrapper (ie, Vector3Codable). However, this means that any of my objects that have these simd members must also provide custom Codable implementations, instead using the default support that the compiler adds in.

I'd prefer to stick with simd if possible, as opposed to providing my own implementation for everything.

Edit - It appears as though my simplified sample code actually does work (doh.). Here is the actual code that doesn't work. Apparently the issue is more complex than I initially thought.

typealias Vector3 = SIMD3<Float>

extension Vector3 {
    enum CodingKeys: String, CodingKey {
        case x
        case y
        case z
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let x = try container.decodeIfPresent(Float.self, forKey: .x) ?? 0.0
        let y = try container.decodeIfPresent(Float.self, forKey: .y) ?? 0.0
        let z = try container.decodeIfPresent(Float.self, forKey: .z) ?? 0.0

        self.init(x, y, z)
    }
}

EDIT after the question was updated

It's true that you can't override SIMD3's codable behaviour in an extension. You can, however, try to wrap your SIMDs in trivial forwarder structs that then allow you to control their encoding:

struct Vector3 { // instead of the typealias
    private var simd: SIMD3<Float>
    var x: Float { return simd.x }
    var y: Float { return simd.y }
    var z: Float { return simd.z }
    // add forwarders for other SIMD methods/properties you use

    init(_ x: Float, _ y: Float, _ z: Float) {
        simd = SIMD3(x, y, z)
    }
}

extension Vector3: Codable {
    enum CodingKeys: String, CodingKey {
        case x, y, z
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let x = try container.decodeIfPresent(Float.self, forKey: .x) ?? 0.0
        let y = try container.decodeIfPresent(Float.self, forKey: .y) ?? 0.0
        let z = try container.decodeIfPresent(Float.self, forKey: .z) ?? 0.0

        self.init(x, y, z)
    }
}

(for brevity, I've only included getters and Decodable )


This works as expected:

struct Obj1 : Codable {
   var values:[Int]
}

extension Obj1 {
    enum CodingKeys: String, CodingKey {
        case x, y, z
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let x = try container.decode(Int.self, forKey: .x)
        let y = try container.decode(Int.self, forKey: .y)
        let z = try container.decode(Int.self, forKey: .z)

        self.values = [x, y, z]
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(values[0], forKey: .x)
        try container.encode(values[1], forKey: .y)
        try container.encode(values[2], forKey: .z)
    }
}

let obj = Obj1(values: [7, 42, 17])

do {
    let data = try JSONEncoder().encode(obj)
    print(String(bytes: data, encoding: .utf8)!) // "{"x":7,"y":42,"z":17}"

    let o = try JSONDecoder().decode(Obj1.self, from: data)
    print(o.values) // [7, 42, 17]
} catch {
    print(error)
}

When your implementations are never called, please show the code that encodes/decodes your obj1 instances.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM