Skip to content

Instantly share code, notes, and snippets.

@staminajim
Created April 26, 2019 06:22

Revisions

  1. staminajim created this gist Apr 26, 2019.
    96 changes: 96 additions & 0 deletions LegacyInt2CodableExample.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,96 @@
    /*
    Example of a way to read in serialized data (eg. json) when Swift adds built in
    Codable / Hashable / Equatible to a struct / class that previously didn't, yet they decided
    to serialize things differently than you did in existing code.

    In this example, I had previously serialized int2 to a dictionary with x and y keys for its
    x and y properties. Swift 5 serializes this as an array, which is incompatible and causes
    the whole json file to fail to deserialize.

    You could write a custom init(from decoder: Decoder for each class using the problematic struct,
    but for some complex classes this can be a *lot* of properties to write boilerplate deserialization code for,
    as that removes *all* of the fun automagic CodingKeys mappings.
    Hopefully one day we can call super.init(from: decoder) and have it fill in the rest of the automatic keys,
    leaving the custom decoded ones untouched.

    */

    /// Wrapper for old style codable int2
    struct LegacyInt2: Codable, Hashable, Equatable {

    var _int2: int2 // sorry not sorry for the underscore

    enum CodingKeys: String, CodingKey {
    case x
    case y
    }

    public init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    let x: Int = try values.decode(Int.self, forKey: .x)
    let y: Int = try values.decode(Int.self, forKey: .y)
    _int2 = int2(x: Int32(x), y: Int32(y))
    }

    public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    let x: Int = Int(_int2.x)
    let y: Int = Int(_int2.y)
    try container.encode(x, forKey: .x)
    try container.encode(y, forKey: .y)
    }

    func hash(into hasher: inout Hasher) {
    hasher.combine(_int2)
    }

    public static func == (lhs: LegacyInt2, rhs: LegacyInt2) -> Bool {
    return lhs._int2 == rhs._int2
    }

    }

    /// Example codable class using an int2 property which will fail to decode old json in swift 5.
    class MapItem: Codable {

    /*
    Old style coordinates were stored in "coord". The only annoying requirement here is the new key
    must be different from the old one. Can be a good opportunity to choose a better name for some
    keys, or shorten overly verbose ones.

    Legacy style structs will be written / read from "coord" key, and new Swift 5 style will use "coordinate".
    */
    private enum CodingKeys: String, CodingKey {
    case _legacyCoord = "coord"
    case _coord = "coordinate"
    }

    private var _legacyCoord: LegacyInt2?
    private var _coord: int2?

    /*
    Use the new style coord if we have one. If not, we must have an old style coord.
    Read it in, and clear out the legacy struct. From here-on only the new style
    will be serialized and deserialized, cleansing it from the stored files each time
    the var is read.

    Eventually you'll be left with no legacy json blobs and can remove the legacy wrapper.
    */
    var coord: int2 {
    get {
    if let _coord = _coord {
    return _coord
    } else if let _legacyCoord = _legacyCoord {
    _coord = _legacyCoord._int2
    self._legacyCoord = nil // only new style coords will be written from here on
    return _coord!
    }
    // you could provide a default value to _coord here if that's what you need.
    // For this example, this object must be read from json, otherwise it will fail.
    fatalError()
    }
    set (newValue) {
    _coord = newValue
    _legacyCoord = nil
    }
    }