Skip to content

Commit

Permalink
Merge pull request #608 from Hearst-DD/immutable-mappable
Browse files Browse the repository at this point in the history
Immutable mappable
  • Loading branch information
tristanhimmelman committed Oct 7, 2016
2 parents 422052f + 2ddaf20 commit e8ae9a6
Show file tree
Hide file tree
Showing 43 changed files with 1,976 additions and 779 deletions.
80 changes: 72 additions & 8 deletions ObjectMapper.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ObjectMapper/Core/FromJSON.swift
Expand Up @@ -6,7 +6,7 @@
//
// The MIT License (MIT)
//
// Copyright (c) 2014-2015 Hearst
// Copyright (c) 2014-2016 Hearst
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand Down
193 changes: 193 additions & 0 deletions ObjectMapper/Core/ImmutableMappble.swift
@@ -0,0 +1,193 @@
//
// ImmutableMappble.swift
// ObjectMapper
//
// Created by Suyeol Jeon on 23/09/2016.
//
// The MIT License (MIT)
//
// Copyright (c) 2014-2016 Hearst
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

public protocol ImmutableMappable: BaseMappable {
init(map: Map) throws
}

public extension ImmutableMappable {

/// Implement this method to support object -> JSON transform.
public func mapping(map: Map) {}

/// Initializes object from a JSON String
public init(JSONString: String, context: MapContext? = nil) throws {
self = try Mapper(context: context).map(JSONString: JSONString)
}

/// Initializes object from a JSON Dictionary
public init(JSON: [String: Any], context: MapContext? = nil) throws {
self = try Mapper(context: context).map(JSON: JSON)
}

}

public extension Map {

fileprivate func currentValue(for key: String) -> Any? {
return self[key].currentValue
}

// MARK: Basic

/// Returns a value or throws an error.
public func value<T>(_ key: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) throws -> T {
let currentValue = self.currentValue(for: key)
guard let value = currentValue as? T else {
throw MapError(key: key, currentValue: currentValue, reason: "Cannot cast to '\(T.self)'", file: file, function: function, line: line)
}
return value
}

/// Returns a transformed value or throws an error.
public func value<Transform: TransformType>(_ key: String, using transform: Transform, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) throws -> Transform.Object {
let currentValue = self.currentValue(for: key)
guard let value = transform.transformFromJSON(currentValue) else {
throw MapError(key: key, currentValue: currentValue, reason: "Cannot transform to '\(Transform.Object.self)' using \(transform)", file: file, function: function, line: line)
}
return value
}

// MARK: BaseMappable

/// Returns a `BaseMappable` object or throws an error.
public func value<T: BaseMappable>(_ key: String) throws -> T {
let currentValue = self.currentValue(for: key)
return try Mapper<T>().mapOrFail(JSONObject: currentValue)
}

// MARK: [BaseMappable]

/// Returns a `[BaseMappable]` or throws an error.
public func value<T: BaseMappable>(_ key: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) throws -> [T] {
let currentValue = self.currentValue(for: key)
guard let jsonArray = currentValue as? [Any] else {
throw MapError(key: key, currentValue: currentValue, reason: "Cannot cast to '[Any]'", file: file, function: function, line: line)
}
return try jsonArray.enumerated().map { i, JSONObject -> T in
return try Mapper<T>().mapOrFail(JSONObject: JSONObject)
}
}

/// Returns a `[BaseMappable]` using transform or throws an error.
public func value<Transform: TransformType>(_ key: String, using transform: Transform, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) throws -> [Transform.Object] {
let currentValue = self.currentValue(for: key)
guard let jsonArray = currentValue as? [Any] else {
throw MapError(key: key, currentValue: currentValue, reason: "Cannot cast to '[Any]'", file: file, function: function, line: line)
}
return try jsonArray.enumerated().map { i, json -> Transform.Object in
guard let object = transform.transformFromJSON(json) else {
throw MapError(key: "\(key)[\(i)]", currentValue: json, reason: "Cannot transform to '\(Transform.Object.self)' using \(transform)", file: file, function: function, line: line)
}
return object
}
}

// MARK: [String: BaseMappable]

/// Returns a `[String: BaseMappable]` or throws an error.
public func value<T: BaseMappable>(_ key: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) throws -> [String: T] {
let currentValue = self.currentValue(for: key)
guard let jsonDictionary = currentValue as? [String: Any] else {
throw MapError(key: key, currentValue: currentValue, reason: "Cannot cast to '[String: Any]'", file: file, function: function, line: line)
}
var value: [String: T] = [:]
for (key, json) in jsonDictionary {
value[key] = try Mapper<T>().mapOrFail(JSONObject: json)
}
return value
}

/// Returns a `[String: BaseMappable]` using transform or throws an error.
public func value<Transform: TransformType>(_ key: String, using transform: Transform, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) throws -> [String: Transform.Object] {
let currentValue = self.currentValue(for: key)
guard let jsonDictionary = currentValue as? [String: Any] else {
throw MapError(key: key, currentValue: currentValue, reason: "Cannot cast to '[String: Any]'", file: file, function: function, line: line)
}
var value: [String: Transform.Object] = [:]
for (key, json) in jsonDictionary {
guard let object = transform.transformFromJSON(json) else {
throw MapError(key: key, currentValue: json, reason: "Cannot transform to '\(Transform.Object.self)' using \(transform)", file: file, function: function, line: line)
}
value[key] = object
}
return value
}

}

public extension Mapper where N: ImmutableMappable {

public func map(JSON: [String: Any]) throws -> N {
return try self.mapOrFail(JSON: JSON)
}

public func map(JSONString: String) throws -> N {
return try mapOrFail(JSONString: JSONString)
}

public func map(JSONObject: Any?) throws -> N {
return try mapOrFail(JSONObject: JSONObject)
}

}

internal extension Mapper where N: BaseMappable {

internal func mapOrFail(JSON: [String: Any]) throws -> N {
let map = Map(mappingType: .fromJSON, JSON: JSON, context: context)

// Check if object is ImmutableMappable, if so use ImmutableMappable protocol for mapping
if let klass = N.self as? ImmutableMappable.Type,
var object = try klass.init(map: map) as? N {
object.mapping(map: map)
return object
}

// If not, map the object the standard way
guard let value = self.map(JSON: JSON) else {
throw MapError(key: nil, currentValue: JSON, reason: "Cannot map to '\(N.self)'")
}
return value
}

internal func mapOrFail(JSONString: String) throws -> N {
guard let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) else {
throw MapError(key: nil, currentValue: JSONString, reason: "Cannot parse into '[String: Any]'")
}
return try mapOrFail(JSON: JSON)
}

internal func mapOrFail(JSONObject: Any?) throws -> N {
guard let JSON = JSONObject as? [String: Any] else {
throw MapError(key: nil, currentValue: JSONObject, reason: "Cannot cast to '[String: Any]'")
}
return try mapOrFail(JSON: JSON)
}

}
37 changes: 4 additions & 33 deletions ObjectMapper/Core/Map.swift
Expand Up @@ -6,7 +6,7 @@
//
// The MIT License (MIT)
//
// Copyright (c) 2014-2015 Hearst
// Copyright (c) 2014-2016 Hearst
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -40,16 +40,13 @@ public final class Map {

public internal(set) var JSON: [String: Any] = [:]
public internal(set) var isKeyPresent = false
public var currentValue: Any?
public var context: MapContext?
public internal(set) var currentValue: Any?
public internal(set) var currentKey: String?
var keyIsNested = false
public var context: MapContext?

let toObject: Bool // indicates whether the mapping is being applied to an existing object

/// Counter for failing cases of deserializing values to `let` properties.
private var failedCount: Int = 0

public init(mappingType: MappingType, JSON: [String: Any], toObject: Bool = false, context: MapContext? = nil) {
self.mappingType = mappingType
self.JSON = JSON
Expand Down Expand Up @@ -99,36 +96,10 @@ public final class Map {
return self
}

// MARK: Immutable Mapping

public func value<T>() -> T? {
return currentValue as? T
}

public func valueOr<T>( _ defaultValue: @autoclosure() -> T) -> T {
return value() ?? defaultValue()
}

/// Returns current JSON value of type `T` if it is existing, or returns a
/// unusable proxy value for `T` and collects failed count.
public func valueOrFail<T>() -> T {
if let value: T = value() {
return value
} else {
// Collects failed count
failedCount += 1

// Returns dummy memory as a proxy for type `T`
let pointer = UnsafeMutablePointer<T>.allocate(capacity: 0)
pointer.deallocate(capacity: 0)
return pointer.pointee
}
}

/// Returns whether the receiver is success or failure.
public var isValid: Bool {
return failedCount == 0
}

}

/// Fetch value from JSON dictionary, loop through keyPathComponents until we reach the desired object
Expand Down
68 changes: 68 additions & 0 deletions ObjectMapper/Core/MapError.swift
@@ -0,0 +1,68 @@
//
// MapError.swift
// ObjectMapper
//
// Created by Tristan Himmelman on 2016-09-26.
//
// The MIT License (MIT)
//
// Copyright (c) 2014-2016 Hearst
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

public struct MapError: Error {
public var key: String?
public var currentValue: Any?
public var reason: String?
public var file: StaticString?
public var function: StaticString?
public var line: UInt?

init(key: String?, currentValue: Any?, reason: String?, file: StaticString? = nil, function: StaticString? = nil, line: UInt? = nil) {
self.key = key
self.currentValue = currentValue
self.reason = reason
self.file = file
self.function = function
self.line = line
}
}

extension MapError: CustomStringConvertible {

private var location: String? {
guard let file = file, let function = function, let line = line else { return nil }
let fileName = ((String(describing: file).components(separatedBy: "/").last ?? "").components(separatedBy: ".").first ?? "")
return "\(fileName).\(function):\(line)"
}

public var description: String {
let info: [(String, Any?)] = [
("- reason", reason),
("- location", location),
("- key", key),
("- currentValue", currentValue),
]
let infoString = info.map { "\($0): \($1 ?? "nil")" }.joined(separator: "\n")
return "Got an error while mapping.\n\(infoString)"
}

}
22 changes: 21 additions & 1 deletion ObjectMapper/Core/Mappable.swift
Expand Up @@ -3,8 +3,28 @@
// ObjectMapper
//
// Created by Scott Hoyt on 10/25/15.
// Copyright © 2015 hearst. All rights reserved.
//
// The MIT License (MIT)
//
// Copyright (c) 2014-2016 Hearst
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

Expand Down
2 changes: 1 addition & 1 deletion ObjectMapper/Core/Mapper.swift
Expand Up @@ -6,7 +6,7 @@
//
// The MIT License (MIT)
//
// Copyright (c) 2014-2015 Hearst
// Copyright (c) 2014-2016 Hearst
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand Down

0 comments on commit e8ae9a6

Please sign in to comment.