Last active
May 29, 2024 06:05
-
-
Save amirdew/ff8e27af5aee5f2bb3c2da2c9f7327cd to your computer and use it in GitHub Desktop.
Modifying private and immutable properties (let) in Codable instances
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
extension Decodable where Self: Encodable { | |
/// Creates a new instance and changes the value for the provided key. | |
/// | |
/// - Parameters: | |
/// - key: The key path to the property that you want to modify. | |
/// Use period to separate levels and [] for indexes. | |
/// Examples: "id", "name.firstName", "children[2].name.firstName" | |
/// | |
/// - value: The new value for the provided key. | |
/// | |
/// - Returns: A new instance with modified property | |
func modifying<T>(_ key: String, to value: T) throws -> Self { | |
let originalData = try JSONEncoder().encode(self) | |
var object = try JSONSerialization.jsonObject(with: originalData, options: []) | |
let keyComponents = keyComponents(from: key) | |
object = modify(object, keyComponents: keyComponents, value: value) | |
let data = try JSONSerialization.data(withJSONObject: object, options: []) | |
return try JSONDecoder().decode(Self.self, from: data) | |
} | |
private func modify<T>(_ object: Any, keyComponents: [KeyComponent], value: T) -> Any { | |
var keyComponents = keyComponents | |
while !keyComponents.isEmpty { | |
let keyComponent = keyComponents.removeFirst() | |
if var array = object as? [Any], case .index(let index) = keyComponent, index < array.count { | |
if keyComponents.isEmpty { | |
array[index] = value | |
} else { | |
var nextObject = array[index] | |
nextObject = modify(nextObject, keyComponents: keyComponents, value: value) | |
array[index] = nextObject | |
} | |
return array | |
} else if var dictionary = object as? [String: Any], case .key(let key) = keyComponent { | |
if keyComponents.isEmpty { | |
dictionary[key] = value | |
} else if var nextObject = dictionary[key] { | |
nextObject = modify(nextObject, keyComponents: keyComponents, value: value) | |
dictionary[key] = nextObject | |
} | |
return dictionary | |
} | |
} | |
return object | |
} | |
private func keyComponents(from key: String) -> [KeyComponent] { | |
let indexSearch = /(.+)\[(\d+)\]/ | |
return key.components(separatedBy: ".").flatMap { keyComponent in | |
if let result = try? indexSearch.wholeMatch(in: keyComponent), let index = Int(result.2) { | |
return [KeyComponent.key(String(result.1)), .index(index)] | |
} | |
return [.key(keyComponent)] | |
} | |
} | |
} | |
private enum KeyComponent { | |
case key(String) | |
case index(Int) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage: