Skip to content

Commit

Permalink
Add nested key path setter support
Browse files Browse the repository at this point in the history
  • Loading branch information
Rob Phillips committed Mar 27, 2017
1 parent e5b4fbf commit c7c21ab
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 16 deletions.
12 changes: 6 additions & 6 deletions LazyObject.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -179,20 +179,20 @@
D4BCBAD31CF0D2530014D111 /* Tests */ = {
isa = PBXGroup;
children = (
D4418C7F1CF4EE7E001A9E94 /* DateTests.swift */,
D4A0F2451E07818600536FD0 /* BoolConversionTests.swift */,
D4418C811CF5E37F001A9E94 /* DateFormatterTests.swift */,
D4418C7F1CF4EE7E001A9E94 /* DateTests.swift */,
D4A0F2421E07815700536FD0 /* DoubleConversionTests.swift */,
D4BCBAEF1CF0D9020014D111 /* ErrorTests.swift */,
D4A0F23F1E0780D200536FD0 /* FloatConversionTests.swift */,
D45BFC3C1CF107BB00914459 /* InstantiationTests.swift */,
D4A0F2391E07802800536FD0 /* IntConversionTests.swift */,
D4BCBAF11CF0E4810014D111 /* LazyValueTests.swift */,
D4BCBAED1CF0D8EE0014D111 /* NSNumberConversionTests.swift */,
D4A0F23C1E07809D00536FD0 /* UIntConversionTests.swift */,
D4A0F2391E07802800536FD0 /* IntConversionTests.swift */,
D4A0F23F1E0780D200536FD0 /* FloatConversionTests.swift */,
D4A0F2421E07815700536FD0 /* DoubleConversionTests.swift */,
D4A0F2451E07818600536FD0 /* BoolConversionTests.swift */,
D49964E21CF36C8400650211 /* NSURLConversionTests.swift */,
D4BCBAEB1CF0D8950014D111 /* NullValueTests.swift */,
D40AD0AD1CF3B3C70093B743 /* SetterTests.swift */,
D4A0F23C1E07809D00536FD0 /* UIntConversionTests.swift */,
D4BCBAD51CF0D2530014D111 /* ValueTypeTests.swift */,
);
path = Tests;
Expand Down
44 changes: 44 additions & 0 deletions Source/Extensions/NSDictionary+Lazy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import Foundation

// MARK: - Pruning

extension NSDictionary {

/**
Expand All @@ -24,6 +26,12 @@ extension NSDictionary {
return nonNullDictionary
}

}

// MARK: - KVO Safety

extension NSDictionary {

/**
Safely returns a value at the given key / key path if it exists
Expand All @@ -39,5 +47,41 @@ extension NSDictionary {
}
return object
}

}

extension NSMutableDictionary {

/// Safely sets a value for a given key path, making existing nodes mutable if necessary
///
/// - Parameters:
/// - value: The value to set
/// - keyPath: The key path to use
func lazySet(value: Any?, forKeyPath keyPath: String) {
_makeDirectoriesMutable(forKeyPath: keyPath)
setValue(value, forKeyPath: keyPath)
}

/// Recursively traverses the given key path and turns any `NSDictionary` objects it finds into `NSMutableDictionary` equivalents so we can write back to the key path
///
/// - Parameter keyPath: The key path to search
fileprivate func _makeDirectoriesMutable(forKeyPath keyPath: String) {
let keys = keyPath.characters.split(separator: ".").map(String.init)
var currentKeyPath: String?

keys.forEach { key in
if currentKeyPath != nil {
let nextKeyPathAddition = String(format: ".%@", key)
currentKeyPath = currentKeyPath?.appending(nextKeyPathAddition)
} else {
currentKeyPath = key
}

if let currentKeyPath = currentKeyPath, let value = lazyValue(forKeyPath: currentKeyPath) as? NSDictionary {
setValue(value.mutableCopy(), forKeyPath: currentKeyPath)
}
}

}

}
12 changes: 4 additions & 8 deletions Source/LazyMapping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,7 @@ public extension LazyMapping {
- parameter keyPath: The key / key path to set the value for
*/
public func setObject(_ object: Any?, keyPath: String) {
if let object = object {
dictionary.setObject(object, forKey: keyPath as NSCopying)
} else {
dictionary.removeObject(forKey: keyPath)
}
dictionary.lazySet(value: object, forKeyPath: keyPath)
}

}
Expand All @@ -198,7 +194,7 @@ private extension LazyMapping {
throw LazyMappingError.conversionError(keyPath: keyPath, value: value, type: T.self)
}

dictionary.setValue(typedValue, forKeyPath: keyPath)
dictionary.lazySet(value: typedValue, forKeyPath: keyPath)
return typedValue
}

Expand All @@ -215,7 +211,7 @@ private extension LazyMapping {

let mappedArray = array.map { T(dictionary: $0, pruneNullValues: true) }

dictionary.setValue(mappedArray, forKeyPath: keyPath)
dictionary.lazySet(value: mappedArray, forKeyPath: keyPath)
return mappedArray
}

Expand All @@ -229,7 +225,7 @@ private extension LazyMapping {

// Otherwise, try to convert it to the given type
let transformedValue = try convert(value)
dictionary.setValue(transformedValue, forKeyPath: keyPath)
dictionary.lazySet(value: transformedValue, forKeyPath: keyPath)
return transformedValue
}

Expand Down
28 changes: 26 additions & 2 deletions Tests/SetterTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,27 @@ final class SetterTests: XCTestCase {
setObject(newValue, setter: #function)
}
}
var nestedId: NSNumber? {
get {
return try? objectFor("nested.identifier")
}
set {
setObject(newValue, keyPath: "nested.identifier")
}
}
}
let object = Object(dictionary: ["id": "42"])

let object = Object(dictionary: ["id": "42",
"nested": ["identifier": "24"]])

func testSettingObjectByKeyPath() {
XCTAssertTrue(object.id != 43)
object.setObject(43, keyPath: "id")
XCTAssertTrue(object.id == 43)

XCTAssertTrue(object.nestedId != 43)
object.nestedId = 43
XCTAssertTrue(object.nestedId == 43)
}

func testSettingObjectBySetter() {
Expand All @@ -35,12 +49,22 @@ final class SetterTests: XCTestCase {
XCTAssertTrue(object.id == 44)
}

func testRemovingObjectUsingNilValueForSetter() {
func testRemovingObjectUsingNilValueForNonNestedSetter() {
// Non-nested type
object.id = 24
XCTAssertTrue(object.id == 24)

object.id = nil
XCTAssertNil(object.id)
}

func testRemovingObjectUsingNilValueForNestedSetter() {
// Nested type
object.nestedId = 42
XCTAssertTrue(object.nestedId == 42)

object.nestedId = nil
XCTAssertNil(object.nestedId)
}

}

0 comments on commit c7c21ab

Please sign in to comment.