Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions JSONAPIModel.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand Down Expand Up @@ -403,7 +403,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
Expand Down
93 changes: 93 additions & 0 deletions JSONAPIModel/Attribute.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import Foundation
import SwiftyJSON

/// Attribute provides value binding between JSON with key to the left-hand-side value
public struct Attribute {
/// JSON object
public let json: JSON
/// Key for reading value from the JSON object
public let key: String
/// Callback for collecting values
public let collectValue: ((Any?) -> Void)?

public init(data: Data, options: JSONSerialization.ReadingOptions = [], key: String) throws {
self.json = try JSON(data: data, options: options)
self.key = key
collectValue = nil
}

public init(key: String, collectValue: @escaping ((Any?) -> Void)) {
self.key = key
self.collectValue = collectValue
json = JSON([])
}

internal init(json: JSON, key: String) {
self.json = json
self.key = key
collectValue = nil
}
}

extension Attribute {
//// Bind value between `json[keyPath]` to the left-hand-side value
//// - Parameters lhs: the left-hande-side value to be bound to
public func bind<T>(_ lhs: inout T) throws {
if collectValue != nil {
try bindToJSON(lhs)
} else {
try bindFromJSON(&lhs)
}
}

//// Bind value between `json[keyPath]` to the left-hand-side optional value
//// - Parameters lhs: the optional left-hande-side optional value to be bound to
public func bind<T>(_ lhs: inout T?) throws {
if collectValue != nil {
try bindToJSON(lhs)
} else {
try bindFromJSON(&lhs)
}
}
}

extension Attribute {
private func bindToJSON<T>(_ lhs: T) throws {
collectValue!(lhs)
}

private func bindToJSON<T>(_ lhs: T?) throws {
guard let value = lhs else {
return
}
collectValue!(value)
}
}

extension Attribute {
private func bindFromJSON<T>(_ lhs: inout T) throws {
let jsonValue = json[key]
guard jsonValue.exists() else {
throw JSONAPIMap.Error.keyError(key: key)
}
guard let value = jsonValue.object as? T else {
throw JSONAPIMap.Error.valueError(key: key)
}
lhs = value
}

private func bindFromJSON<T>(_ lhs: inout T?) throws {
let jsonValue = json[key]
guard jsonValue.exists() else {
return
}
guard jsonValue.null == nil else {
lhs = nil
return
}
guard let value = jsonValue.object as? T else {
return
}
lhs = value
}
}
51 changes: 32 additions & 19 deletions JSONAPIModel/JSONAPIFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,34 @@ import SwiftyJSON

/// Factory for making JSON API models
public final class JSONAPIFactory: Collection {
public typealias Storage = [String: JSONAPIModelType.Type]
public typealias Index = Storage.Index
public typealias Element = Storage.Element
public typealias Indices = Storage.Indices
public typealias Storage = [String: JSONAPIModelType.Type]
public typealias Index = Storage.Index
public typealias Element = Storage.Element
public typealias Indices = Storage.Indices

private var models: Storage = [:]

public init() {}

public var startIndex: Storage.Index { models.startIndex }
public var endIndex: Storage.Index { models.endIndex }
public var indices: Storage.Indices { models.indices }
public var startIndex: Storage.Index { models.startIndex }
public var endIndex: Storage.Index { models.endIndex }
public var indices: Storage.Indices { models.indices }

public subscript(position: Storage.Index) -> Storage.Element {
models[position]
}
public subscript(position: Storage.Index) -> Storage.Element {
models[position]
}

public func index(after i: Storage.Index) -> Storage.Index {
models.index(after: i)
}
public func index(after i: Storage.Index) -> Storage.Index {
models.index(after: i)
}

/// Register a model type to `self` factory
/// - Parameters modelType: type of model to register
public func register(modelType: JSONAPIModelType.Type) {
let metadata = modelType.metadata
if models[metadata.type] != nil {
fatalError("Model \(metadata.type) already registered")
assertionFailure("Model \(metadata.type) already registered")
return
}
models[metadata.type] = modelType
}
Expand All @@ -48,7 +49,7 @@ public final class JSONAPIFactory: Collection {
/// - Returns: created JSON API model, if the type does not exist, nil will be returned
public func createModel(id: String, type: String) -> JSONAPIModelType? {
guard let modelType = models[type] else {
assertionFailure("failed to create model for \(type). Make sure you registered the model type")
assertionFailure("failed to create model for \(type). Make sure you registered the model type")
return nil
}
return modelType.init(id: id)
Expand All @@ -59,11 +60,23 @@ public final class JSONAPIFactory: Collection {
/// - Parameters id: id of model to create
/// - Parameters type: type of model to create
/// - Returns: created JSON API model, if the type does not exist, nil will be returned
public func createModel(_ json: JSON) throws -> JSONAPIModelType? {
public func createModel(_ data: Data, options: JSONSerialization.ReadingOptions = []) throws -> JSONAPIModelType? {
let json = try JSON(data: data, options: options)

return try createModel(json)
}

public func createModel(object: Any) throws -> JSONAPIModelType? {
let json = JSON(object)

return try createModel(json)
}

internal func createModel(_ json: JSON) throws -> JSONAPIModelType? {
guard
let id = json["id"].string,
let modelType = json["type"].string
else {
let id = json["id"].string,
let modelType = json["type"].string
else {
return nil
}
guard let model = createModel(id: id, type: modelType) else {
Expand Down
69 changes: 69 additions & 0 deletions JSONAPIModel/JSONAPIMap.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Foundation
import SwiftyJSON

/// JSONAPIMap provides `attribute` mapping methods for `JSONAPIModelType.mapping` method to define
/// attribute binding
public final class JSONAPIMap {
public static let attributesKey = "attributes"

public enum Error: Swift.Error {
case keyError(key: String)
case valueError(key: String)
}

/// Collected attributes for reading mode
public private(set) var collectedAttributes: [String: Any]?

private let json: JSON

public init(data: Data, options: JSONSerialization.ReadingOptions = []) throws {
self.json = try JSON(data: data, options: options)
}

internal init(_ json: JSON) {
self.json = json
collectedAttributes = nil
}

public init() {
json = JSON([])
collectedAttributes = [:]
}

/// Define attribute binding
/// - Parameters key: the key in `attributes` JSON dict for binding value from
/// - Returns: an Attribute object for the attribute binding
public func attribute(_ key: String) -> Attribute {
if collectedAttributes != nil {
return Attribute(key: key) { value in
self.collectedAttributes![key] = value
}
} else {
return Attribute(json: json[JSONAPIMap.attributesKey], key: key)
}
}

/// Define attribute binding using data transformer
/// - Parameters key: the key in `attributes` JSON dict for binding value from
/// - Parameters using: the TransformType to be used for transforming binding data
/// - Returns: an Attribute object for the attribute binding
public func attribute<TransformType: Transform>(
_ key: String,
using transform: TransformType
) -> TransformedAttribute<TransformType> {
if collectedAttributes != nil {
return TransformedAttribute(
key: key,
transform: transform
) { value in
self.collectedAttributes![key] = value
}
} else {
return TransformedAttribute(
json: json[JSONAPIMap.attributesKey],
key: key,
transform: transform
)
}
}
}
Loading