This is the 14th day of my participation in Gwen Challenge
What is Codable?
/// A type that can convert itself into and out of an external representation.
///
/// `Codable` is a type alias for the `Encodable` and `Decodable` protocols.
/// When you use `Codable` as a type or a generic constraint, it matches
/// any type that conforms to both protocols.
typealias Codable = Decodable & Encodable
protocol Encodable {
func encode(to encoder: Encoder) throws
}
protocol Decodable {
init(from decoder: Decoder) throws
}
Copy the code
You can see that it is actually a composite interface of Decodable and Encodeable for data parsing and encoding; Codable interface is available in the Swift4.0 standard library;
Take a quick look at a Model to JSON example to get a feel for official Codable logic
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let banner = GroceryProduct(name: "Banana", points: 250, description: "Made in Hainan")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
/// Encode the banner instance with JSON
let data = try encoder.encode(banner)
print(String(data: data, encoding: .utf8)!)
/* Print: {"name" : "Pear", "points" : 250, "description" : "A ripe Pear."} */
// convert JSON String to Model instance
let json = "" "{" name ":" durian ", "points" : 600, "description" : "it is difficult to accept the fruits of the common man"} "" ".data(using: .utf8)!
let decoder = JSONDecoder(a)let product = try decoder.decode(GroceryProduct.self, from: json)
print(product.name) // Print "durian"
Copy the code
Filter coding attribute
You can filter out some of the fields in json serialization by declaring a CodingKeys enumeration property
struct GroceryProduct: Codable {
var name: String
var points: Int = 0
var description: String?
enum CodingKeys: String.CodingKey {
case name
case description
}
}
Copy the code
The output
{"name":"Banana"."description":"Made in Hainan"}
Copy the code
The key value to modify
Sometimes the name of the field delivered by the service is not what we want. For example, the interface uses gender to indicate gender, while some interfaces use sex. In order to accommodate different interfaces, we can also change the name of the encoding property through CodingKeys
struct GroceryProduct: Codable {
var name: String
var points: Int = 0
var description: String?
enum CodingKeys: String.CodingKey {
case name
case description = "desc"}}Copy the code
Output result:
{"name":"Banana"."desc":"Made in Hainan"}
Copy the code
Key value policy customization
In everyday use, we will encounter different naming conventions, such as person_name and personName, and personName with uppercase letters. If you and the back end do not have the same naming style, custom keys may compensate.
The first way is to modify it directly in CodingKeys:
struct Person: Codable {
var firstName: String
var secondName: String
enum CodingKeys: String.CodingKey {
case firstName = "first_name"
case secondName = "second_name"}}Copy the code
The second approach is to address key strategies for decoding and encoding JSON using a more general key-value conversion strategy.
/// The strategy to use for automatically changing the value of keys before encoding.
public enum KeyEncodingStrategy {
.
/// Provide a custom conversion to the key in the encoded JSON from the keys specified by the encoded types.
case custom(([CodingKey]) -> CodingKey)}/// The strategy to use for automatically changing the value of keys before decoding.
public enum KeyDecodingStrategy {
.
/// Provide a custom conversion from the key in the encoded JSON to the keys specified by the decoded types.
case custom(([CodingKey]) -> CodingKey)}Copy the code
// Json encoding of the Person instance
let encoder = JSONEncoder()
encoder.keyEncodingStrategy =The customlet data = try! encoder.encode(person)
let personEncodedString = String(data: data, encoding: .utf8)!
print(personEncodedString)
Copy the code
Structure customization
struct Person: Codable {
var gender: String
var age: Int
var name: String
var firstName: String
var secondName: String
enum CodingKeys: String.CodingKey {
case gender
case age
case name
}
enum NameKeys: String.CodingKey {
case firstName
case secondName
}
}
extension Person {
/ / parsing
init(from decoder: Decoder) throws {
let vals = try decoder.container(keyedBy: CodingKeys.self)
gender = try vals.decode(String.self, forKey: CodingKeys.gender)
age = try vals.decode(Int.self, forKey: CodingKeys.age)
// nestedContainer resolves the name attribute
let name = try vals.nestedContainer(keyedBy: NameKeys.self, forKey: .name)
firstName = try name.decode(String.self, forKey: .firstName)
secondName = try name.decode(String.self, forKey: .secondName)
}
/ / code
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(gender, forKey: .gender)
try container.encode(age, forKey: .age)
//nestedContaine resolves the name node
var name = container.nestedContainer(keyedBy: NameKeys.self, forKey: .name)
try name.encode(firstName, forKey: .firstName)
try name.encode(secondName, forKey: .secondName)
}
}
Copy the code
Tripartite JSON parsing library
In OC, we have many excellent third-party libraries to help us achieve this, such as MJExtension, JSONModel, etc. These libraries basically use Runtime to read attribute names and use KVC to reassign attributes.
In Swift, due to the limitations of runtime, there are relatively famous SwiftyJSON, ObjectMapper, HandyJSON, etc.
Among them
1, SwiftyJSON is still based on the JSON structure to value, easy to use, clear;
2, ObjectMapper implements JSON directly to Model function, but use, the amount of code will be a little more, because we must follow the Mappable protocol, develop the corresponding relationship between each key and Model attribute in JSON.
3. HandyJSON takes a new approach, using Swift reflection + memory assignment to construct Model instances, keeping the original Definition of Swift class.
HandyJSON
// Assume that this is the uniformly defined response format returned by the server
class BaseResponse<T: HandyJSON> :HandyJSON {
var code: Int? // Server return code
var data: T? // The format of specific data is business dependent, so use generic definition
public required init(a){}}// Suppose this is a business-specific data format definition
struct SampleData: HandyJSON {
var id: Int?
}
let sample = SampleData(id: 2)
let resp = BaseResponse<SampleData>()
resp.code = 200
resp.data = sample
let jsonString = resp.toJSONString()! // Convert from object instance to JSON string
print(jsonString) // print: {"code":200,"data":{"id":2}}
if let mappedObject = JSONDeserializer<BaseResponse<SampleData>>.deserializeFrom(json: jsonString) { // Convert from string to object instance
print(mappedObject.data?.id)
}
Copy the code
- HandyJSON supports JSON directly to Model. When defining a class, there are two things to note:
- You must follow the HandyJSON protocol
- You need to implement an empty initializer (of course you don’t need init() for Struct structures)
-
HandyJSON also supports structs, which are used in much the same way as classes
-
HandyJSON supports enumeration, so you only need to follow the HandyJSONEnum protocol when constructing the enum.
enum AnimalType: String.HandyJSONEnum { case Cat = "cat" case Dog = "dog" case Bird = "bird" } struct Animal: HandyJSON { var name: String? var type: AnimalType? } let jsonString = "{\"type\":\"cat\".\"name\":\"Tom\"}" if let animal = Animal.deserialize(from: jsonString) { print(animal.type?.rawValue) } Copy the code
-
HandyJSON also supports non-basic, complex types, including nested structures such as optional, implicit unpacking optional, collections, and so on
-
HandyJSON supports specifying which concrete path to start parsing and deserialize to Model.
class Cat: HandyJSON { var id: Int64! var name: String! required init(a){}}let jsonString = "{\"code\": 200,\"msg\":\"success\".\"data\": {\"cat\": {\"id\": 12345,\"name\":\"Kitty\"}}}" /// Where, designatedPath is used directly to locate the desired node. if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString, designatedPath: "data.cat") { print(cat.name) } Copy the code
-
HandyJSON supports Model classes with inheritance relationships, which means that even if a class does not implement the HandyJSON protocol, as long as the parent class has an implementation, the Model can still be converted.
-
HandyJSON also supports object-to-dictionary and object-to-model.
class BasicTypes: HandyJSON { var int: Int = 2 var doubleOptional: Double? var stringImplicitlyUnwrapped: String! required init(a){}}let object = BasicTypes() object.int = 1 object.doubleOptional = 1.1 object.stringImplicitlyUnwrapped ="Hello!" print(object.toJSON()!) Print (object.tojsonString ()!) Print (Object.tojsonString (prettyPrint: true)!) // Serialize to a formatted JSON stringCopy the code
conclusion
For simple data parsing, Codable is sufficient
HandyJSON is recommended for large projects with a large number of complex JSON objects and customization requirements
reference
www.jianshu.com/p/661b1539d…
www.devler.cn/blog/1
Developer.apple.com/documentati…
www.jianshu.com/p/661b1539d…