JSON data serialization is one of the biggest challenges in Swift development. Swift is type-safe, which makes it difficult to handle weak types like JSON. One of the new features Swift 4 brings is the Codable protocol. However, Codable didn’t fully meet our requirements, including no automatic conversion of types and unfriendly default support. So, if we solve these problems, won’t it be perfect
Codable pit point 1: Type conversion is not supported
// JSON:
{
"uid":"123456",
"name":"Harry",
"age":10
}
// Model:
struct Dog: Codable{
var uid: Int
var name: String?
var age: Int?
}
Copy the code
In the json conversion process, we often encounter situations where the type model is inconsistent with the type of JSON, such as the uid field above. Uid in JSON is String, but our model is Int. Since Swift is type-safe, the conversion will not succeed.
Codable pit point 2: Default values are not supported
Without further ado, go to the code
struct Activity: Codable {
enum Status: Int {
case start = 1// Start the activity
case processing = 2// The event is in progress
case end = 3// End of activity
}
var name: String
var status: Status// Active status
}
Copy the code
There’s an activity, there’s an activity in three different states, and so far, so good. One day, it suddenly said that the activity needs to add the removed status, what?
//JSON {"name": "New Year's day activity ", "status": 4}Copy the code
Using the Activity to parse the JSON file above will result in an error. How can we avoid it, as shown below
var status: Status?
Copy the code
The answer is no, no, no, because the decoding of the optional value says’ set to nil if it doesn’t exist ‘, not ‘set to nil if decoding fails’.
The solution
Is there a better way to deal with both of these problems? The answer is to use property Wrapper. See ObjMapper for the code, and here’s a brief description of how to use it.
Conversion between Model and JSON
// JSON:
{
"uid":888888."name":"Tom"."age":10
}
// Model:
struct Dog: Codable{
// If the field is not an optional type, use Default, providing a Default value, as shown below
@Default<Int.Zero> var uid: Int
// If the type is optional, use Backed
@Backed var name: String?
@Backed var age: Int?
}
//JSON to model
let dog = Dog.decodeJSON(from: json)
//model to json
let json = dog.jsonString
Copy the code
When the object type in the JSON/Dictionary is inconsistent with the Model property, ObjMapper will perform the following automatic conversion. Values that are not supported by automatic conversion will be set to nil or default.
JSON/Dictionary | Model |
---|---|
String | String, Number (integer, floating point), Bool |
Number type (integer, floating point) | Number type, String, Bool |
Bool | Bool, String, Number (including integer, floating point) |
nil | nil,0 |
The Model of nested
{
"author": {
"id": 888888."name": "Alex"."age": "10"
},
"title": "Model and JSON interchange"."subTitle": "How to transition gracefully."
}
// Model:
struct Author: Codable{
@Default<Int.Zero> var uid: Int
@Default<String.Empty> var name: String
// Backed, if the types do not match, type conversion is performed
@Backed var age: Int?
}
struct Article: Codable {
// If title in JSON is nil or does not exist, a default value is assigned to title
@Default<String.Empty> var title: String
var subTitle: String?
var author: Author
}
//JSON to model
let article = Article.decodeJSON(from: json)
//model to json
let json = article.jsonString
Copy the code
The default value for type
Struct Activity: Codable {///Step 1: make Status follow the DefaultValue protocol enum Status: Int, Codable, DefaultValue {case start = 1 case processing = 2 case end = 3 case unknown = 0// DefaultValue, Static let DefaultValue = status.unknown} @default <String.Empty> var name: static let DefaultValue = status.unknown} @default <String.Empty> var name: static let DefaultValue = status.unknown} @default <String. String ///Step 3: use default@default <Status> var Status: Status// active Status} // support for optional values let json = """ {"name": DecodeJSON (from: json) decodeJSON(from: json)! /// Print ("json: \(activity.jsonString??)") /// Print ("json: \(activity.jsonString?? "")")Copy the code
Set different defaults for common types
ObjMapper already has a lot of default values built in, such as Int.Zero, Bool.True, string.empty… If we want to set different defaults for fields, see the following code:
public extension Int { enum One: DefaultValue { public static let defaultValue = 1 } } struct Dog: Codable{ @Backed var name: String? @default <Int.One> var age: Int} @default <Int.One> var age: Int}Copy the code
Reference documentation
- Fast JSON parsing with the Codable protocol
- Swift 4 Tap holes for Codable protocols
- Set defaults for Codable decoding using Property Wrapper
If you don’t like it, please leave a comment at πππ. Welcome β¨β¨β¨starβ¨β¨β¨ and PR