Skip to content

Swift lightweight json serialization, An extension based on Codable, lightweight json and model conversion, Swift轻量级JSON序列化

License

Notifications You must be signed in to change notification settings

ninefivefly/ObjMapper

Repository files navigation

ObjMapper

Version License Platform

ObjtMapper is an extended framework based on Swift's Codable protocol, which allows you to easily convert model objects (classes and structures) to and from JSON without side effects.

Language: English|中文简体

Features

  • Codable-based extensions
  • JSON mapping to object
  • object is mapped to JSON
  • Nested objects
  • supports nil objects
  • supports default values
  • supports array defaults
  • Support conversion between String and integer, floating point number, Bool

Install

CocoaPods

pod 'ObjMapper'

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/ninefivefly/ObjMapper.git", '3.0.0')
]

Preface

Why do we have this library?

In Swift development, JSON data serialization is an unavoidable task. Due to the type safety feature of Swift, it has always been a headache to process weakly typed data like JSON. Among the new features brought by Swift 4 , the Codable protocol makes people shine. However, Codable cannot fully meet our requirements, such as not supporting automatic conversion of types, and not being friendly to default value support. So, if we solve these problems, will it be perfect? How to use the Codable protocol gracefully

Tutorial

1. Convert between Model and JSON

// JSON:
json = """
{
     "uid":888888,
     "name": "Tom",
     "age": 10
}
"""

// Model:
struct Dog: Codable{
     //If the field is not an optional type,
     //use Default and provide a default value, like the following
     @Default<Int.Zero> var uid: Int
     //If it is an optional type, 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

When the object type in JSON/Dictionary is inconsistent with the Model property, ObjMapper will automatically convert as follows. Values not supported by automatic conversion will be set to nil or the default value.

JSON/Dictionary Model
String String, Number type (including integer, floating point number), Bool
Number type (including integer, floating point number) Number type, String, Bool
Bool Bool, String, Number types (including integers, floating point numbers)
nil nil,0

2. Model nesting

let raw_json = """
{
     "author": {
         "id": 888888,
         "name": "Alex",
         "age": "10"
     },
     "title": "model and json conversion",
     "subTitle":"How to convert gracefully"
}
"""

// Model:
struct Author: Codable{
     @Default<Int.Zero> var id: Int
     @Default<String.Empty> var name: String
     // After using Backed, if the type does not match, the type will be automatically converted
     //For example, in the above json, age is a string, and the model we defined is Int,
     //Then after declaring @Backed, it will be automatically converted to Int type
     @Backed var age: Int?
}

struct Article: Codable {
     //If the title in json is nil or does not exist, a default value will be assigned to the title
     @Default<String.Empty> var title: String
     var subTitle: String?
     var author: Author
}

//JSON to model
let article = Article.decodeJSON(from: raw_json)

//model to json
let json = article.jsonString
print(article?.jsonString ?? "")

3. Optional values of custom types

Without further ado, let's go to the code

struct Activity: Codable {
     enum Status: Int {
         case start = 1//Activity starts
         case processing = 2//Activity in progress
         case end = 3//end of event
     }
    
     @Default<String.Empty> var name: String
     var status: Status//active status
}

Here's an activity that currently has three states, so far so good. One day, I suddenly said that I need to add a delisted status to the event, what?

//JSON
{
     "name": "New Year's Day Welcome Event",
     "status": 4
}

Using Activity to parse the above JSON will report an error, how can we avoid it, like the following

var status: Status?

The answer is no, no, no, because the decoding of the optional value expresses "if it does not exist, set it to nil" instead of "if the decoding fails, set it to nil", then use our Default, Please see the code below:

struct Activity: Codable {
     ///Step 1: Let Status follow the DefaultValue protocol
     enum Status: Int, Codable, DefaultValue {
         case start = 1//Activity starts
         case processing = 2//Activity in progress
         case end = 3//end of event
         case unknown = 0//default value, meaningless
        
         ///Step 2: Implement the DefaultValue protocol and specify a default value
         static func defaultValue() -> Status {
             return Status.unknown
         }
     }
    
     @Default<String.Empty> var name: String
     ///Step 3: Use Default
     @Default<Status> var status: Status//active status
}

//{"name": "New Year's Day Welcome Event", "status": 4 }
//Activity will parse the status into unknown

4. Set different default values for common types

This library has built-in many default values, such as Int.Zero, Bool.True, String.Empty..., if we want to set a different default value for the field, see the following code:

public extension Int {
     enum One: DefaultValue {
         static func defaultValue() -> Int {
             return 1
         }
     }
}

struct Dog: Codable{
     @Backed var name: String?
     @Default<Int.Zero> var uid: Int
     //If there is no age field in json or the parsing fails, the age of the model is set to the default value 1
     @Default<Int.One> var age: Int
}

5. Array support

For arrays, @Backed, @Default can be used to resolve

// JSON:
let raw_json = """
{
     "code": 0,
     "message": "success",
     "data": [{
         "name": "New Year's Day Welcome Event",
         "status": 4
     }]
}
"""

struct Activaty: Codable{
     @Default<String.Empty> var name: String
     @Default<Int.Zero> var status: Int
}

// If the array is an optional type, you can use @Backed
struct Response1: Codable {
     @Default<Int.Zero> var code: Int
     @Default<String. Empty> var message: String
     @Backed var data: [Activaty]?
}

// For the array, set the default value, if the array does not exist or the parsing error, use the default value
struct Response2: Codable {
     @Default<Int.Zero> var code: Int
     @Default<String.Empty> var message: String
     @Default<Array.Empty> var data: [Activaty]
}
//JSON to model
let rsp1 = Response1.decodeJSON(from: raw_json)
let rsp2 = Response2.decodeJSON(from: raw_json)

//model to json
let json1 = rsp1.jsonString
let json2 = rsp2.jsonString
// print(rsp1?.jsonString ?? "")
// print(rsp2?.jsonString ?? "")

6. Set the general type

During the development process, the first JSON we encounter may look like this:

// JSON:
{
     "code": 0,
     "message": "success",
     "data":[]//This data can be of any type
}

Since the type of the data field is not fixed, sometimes for unified processing, we define the model as follows, represented by the enumeration type JsonValue.

struct Response: Codable {
     var code: Int
     var message: String
     var data: JsonValue?
}

If you want to get the value of the data field, we can use data?.intValue or data?.arrayValue, etc., see the source code for specific usage.

Note: This is a simple model for data (such as an integer, string, etc.), which can achieve twice the result with half the effort; if the data is a large model, it is recommended to specify the data as a specific type.

7. If you are upgrading from 1.0.x to 2.0, the DefaultValue protocol has been modified. If the DefaultValue protocol is used in the previous code, an error will be reported, and the modification is as follows:

Originally:
///Step 1: Let Status follow the DefaultValue protocol
enum Status: Int, Codable, DefaultValue {
     case start = 1//Activity starts
    
     ///Step 2: Implement the DefaultValue protocol and specify a default value
     static let defaultValue = Status.unknown
}

changed to:
///Step 1: Let Status follow the DefaultValue protocol
enum Status: Int, Codable, DefaultValue {
     case start = 1//Activity starts
    
     ///Step 2: Implement the DefaultValue protocol and return a default value
     static func defaultValue() -> Status {
         return Status.unknown
     }
}

ps: Don’t spray if you don’t like it, please leave a message if you have any questions 😁😁😁, welcome ✨✨✨star✨✨✨ and PR

Author

JIANG PENG CHENG, [email protected]

License

ObjMapper is available under the MIT license. See the LICENSE file for more info.

About

Swift lightweight json serialization, An extension based on Codable, lightweight json and model conversion, Swift轻量级JSON序列化

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published