Tai-Lun Tseng teaualune@gmail.com
And what problem does it tempt to solve?
|
|
NSError *error;
User *user = [[User alloc] initWithString:jsonString error:&error];
NSDictionary *userDictionary = [user toDictionary];
String *stringifiedUser = [user toJSONString];
It's natural!
NSURLResponse
wrapperContent-Type: application/json
automatically in request headersobjc_getAssociatedObject
to get property namesLet's see some popular building blocks that tackled this problem:
ObjectMapper.Mappable
protocol:required init?(map: ObjectMapper.Map) {}
mutating func mapping(map: ObjectMapper.Map) {
userId <- map["id"]
name <- map["name"]
}
Argo.Decodable
protocol:extension User: Decodable {
static func decode(j: JSON) -> Decoded<User> {
return curry(User.init)
<^> j <| "id"
<*> j <| "name"
<*> j <|| "friends"
}
}
Unbox.Unboxable
protocol:init(unboxer: Unboxer) throws {
self.userId = try unboxer.unbox(key: "id")
self.name = try unboxer.unbox(key: "name")
}
Why do we need to write additional mapping logics? Unlike JSONModel, which only requires defining our @property
name well?
Before answering the question, let's see some case studies in other languages:
public class User implements Serializable {
private long id;
@Expose
@SerializedName("id")
private long userId;
@Expose
@SerializedName("name")
private String name;
@Expose
@SerializedName("friends")
private List<User> friends;
}
public interface APIEndpoint {
@GET("/users")
Call<List<User>> getUsers();
@PUT("/users/{userId}")
Call<User> updateUser(@Path("userId") String userId, @Body User user);
}
type User struct {
UserId uint64 `json:"id"`
Name string `json:"name"`
}
func UpdateUserHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user := schema.User{UserId: ps.ByName("userId")}
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&user); err != nil {
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "request body error")
} else { /* ... */ }
}
Since JSON (= Java Script Object Notation) comes from JavaScript, we don't need additional libraries:
user = JSON.parse(userStr);
userStr = JSON.stringify(user);
// user = { id: 1, name: 'Lawrence' }
// userStr = '{"id":1,"name":"Lawrence"}'
Back to Swift, what JSONModel alternative library features do we want?
@property
name and everything is set.id
(JSON) touserId
(property name) mappingDate
objectsNSURL
objectsObjectMapper.Map
instead of built-in NSDictionary
to store raw JSONUse built-in extension to configure Siesta
let conf = URLSessionConfiguration.default
conf.timeoutIntervalForRequest = 60
conf.timeoutIntervalForResource = 60
self.service = Service(baseURL: "https://my.api.com/api/v1",
useDefaultTransformers: true,
networking: AlamofireProvider(configuration: conf))
self.onCompletion { responseInfo in
switch responseInfo.response {
case .success(let entity):
let json: SwiftyJSON.JSON
if let _json = entity.content as? SwiftyJSON.JSON {
json = _json
} else {
json = SwiftyJSON.JSON(entity.content)
}
var ent = Siesta.Entity<SwiftyJSON.JSON>(content: json,
contentType: entity.contentType)
ent.headers = entity.headers
ent.charset = entity.charset
ent.timestamp = entity.timestamp
o.onNext(ent)
case .failure(let error):
o.onError(self.parseLadderError(error) ?? error)
}
o.onCompleted()
}
extension Resource {
func swiftyJSONRequest(_ method: RequestMethod = .get,
data: JSON? = nil) -> Request {
if method != .get, let _data = data {
return self.request(method, text: _data.stringify,
contentType: "application/json")
} else {
return self.request(.get)
}
}
}
By utilizing Swift Mirror API, we can get object information in run-time:
|
|
protocol SwiftyJSONModel {
init(from json: JSON)
mutating func from(_ json: JSON)
}
struct User: SwiftyJSONModel {
init(from json: JSON) {
self.from(json)
}
mutating func from(_ json: JSON) {
self.id = json["id"].uInt64Value
self.name = json["name"].stringValue
}
}
Usage example, combined with generic type class to ensure flexibility
class ResponseTransformer<T: SwiftyJSONModel> {
static func transform(_ json: JSON) -> T {
return T.self.init(from: json)
}
}
let user = ResponseTransformer<User>.transform(json)
Two ways to implement this feature:
func to() -> JSON
in SwiftyJSONModel protocolUserDefaults
directlyFeedbacks are welcome: pics.ee/teaualune