Skip to content

Instantly share code, notes, and snippets.

@staminajim
Created April 26, 2019 06:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save staminajim/dbc33e48f45f41ec54ee0f38a967a436 to your computer and use it in GitHub Desktop.
Save staminajim/dbc33e48f45f41ec54ee0f38a967a436 to your computer and use it in GitHub Desktop.
A strategy for dealing with structs that have been made Codable in a Swift update, but clash with your existing implementation
/*
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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment