background
Check out an interesting example for Swift Codable apps (see resources below). Here’s what the question looks like:
The field returned by the back end does not match the type agreed upon by the original protocol
- Expected: Object type
- Actual: String
This causes the Swift Model to declare this field inconvenient
The reasons for this inconvenience are:
- 1. If the attribute is declared as String, it needs to be reprocessed from String to the desired model
- 2. If you declare it as a Model type, decode will fail in Codable due to strong verification requirements for types
Regardless of the rationality of sending JSON strings from the back end, you can try the following two solutions to solve the problem.
Option 1: Custom Decodable implementation
Swift Codable is the protocol layer that Swift encods and decodes between data and model. For convenience, the compiler automatically assembles Encodable and Decodable implementations of the model for model reporting and following Codable for model. There is also a lot of room for customization within specific custom types. Around this case, a preliminary exploration. JSONDecoder will throw an error decoding failure if the data type returned is not the declared expected type. The possible solution here is to complete a custom Decoable implementation for the Model. Supplementary type declarations are as follows:
struct Token: Codable {
let result: Bool?
// Body object is expected, but string is returned
let body: Body?
}
struct Body: Codable {
let serialNumber: String?
let timestamp: String?
}
Copy the code
The body attribute is the field that needs custom implementation, which is defined in Codable and is the outer type Token. The custom implementation is as follows:
Throw (from decoder: decoder) throws {let container = try decoder. Container (keyedBy: CodingKeys.self) self.result = try container.decodeIfPresent(Bool.self, forKey: .result) let jsonString = try container.decodeIfPresent(String.self, forKey: .body) let jsonStringData = jsonString?.data(using: .utf8) self.body = jsonStringData.flatMap return try? JSONDecoder().decode(Body.self, from: $0) } }Copy the code
The main process here is:
- Custom decodable implementation
- Decode this key as a string first
- Decode the secondary Body object after converting string to data
Scheme 2: further encapsulates the decoding process of JSON String
The problem with the above custom scheme is that it cannot be reused, and if you have multiple fields or multiple models, you need to duplicate the code. In real development, a more elegant solution would be to reprocess the property using a Property wrapper to declare the target type wrapped by the property
struct Token {
@JSONString
var body: Body?
}
Copy the code
At its heart lies the property Wrapper feature:
- Decoding is performed first instead of body type under Codable protocol.
- A string -> model type conversion is performed inside the Wrapper
- The compiler via wrappedValue supports seamless retrieval of fields of the target type
A possible implementation of JSONString Property Wrapper is as follows
@propertyWrapper
public struct JSONString<Base: Codable>: Codable {
public var wrappedValue: Base
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self),
let data = string.data(using: .utf8) {
self.wrappedValue = try LionCoderFactory.newJSONDecoder().decode(Base.self, from: data)
return
}
self.wrappedValue = try container.decode(Base.self)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let data = try LionCoderFactory.newJSONEncoder().encode(wrappedValue)
if let string = String(data: data, encoding: .utf8) {
try container.encode(string)
}
}
}
Copy the code
This property Wrapper has additional support compared to the previous implementation of custom decodable:
- Support extension of generic types to become generic
- Both string and object can be delivered internally
The code will be maintained on GitHub.
summary
There’s a lot to explore in Codable design with Swift’s language features, as well as open source extensions for Codable, such as BetterCodable, and more. Such extensibility leaves plenty of room for enhanced development in API designs with strong typing requirements.
The resources
Original case: “The background returned a JSON that I hated”
Encoding and Decoding Custom Types
Open source library example: BetterCodable