DictionaryKeyPath.swift 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. // LoopFollow
  2. // DictionaryKeyPath.swift
  3. // Created by Jon Fawcett on 2020-06-11.
  4. // For details, see
  5. // http://stackoverflow.com/questions/40261857/remove-nested-key-from-dictionary
  6. import Foundation
  7. extension Dictionary {
  8. subscript(keyPath keyPath: String) -> Any? {
  9. get {
  10. guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath)
  11. else { return nil }
  12. return getValue(forKeyPath: keyPath)
  13. }
  14. set {
  15. guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath),
  16. let newValue = newValue else { return }
  17. setValue(newValue, forKeyPath: keyPath)
  18. }
  19. }
  20. private static func keyPathKeys(forKeyPath: String) -> [Key]? {
  21. let keys = forKeyPath.components(separatedBy: ".")
  22. .reversed().flatMap { $0 as? Key }
  23. return keys.isEmpty ? nil : keys
  24. }
  25. // recursively (attempt to) access queried subdictionaries
  26. // (keyPath will never be empty here; the explicit unwrapping is safe)
  27. private func getValue(forKeyPath keyPath: [Key]) -> Any? {
  28. guard let value = self[keyPath.last!] else { return nil }
  29. return keyPath.count == 1 ? value : (value as? [Key: Any])
  30. .flatMap { $0.getValue(forKeyPath: Array(keyPath.dropLast())) }
  31. }
  32. // recursively (attempt to) access the queried subdictionaries to
  33. // finally replace the "inner value", given that the key path is valid
  34. private mutating func setValue(_ value: Any, forKeyPath keyPath: [Key]) {
  35. guard self[keyPath.last!] != nil else { return }
  36. if keyPath.count == 1 {
  37. (value as? Value).map { self[keyPath.last!] = $0 }
  38. } else if var subDict = self[keyPath.last!] as? [Key: Value] {
  39. subDict.setValue(value, forKeyPath: Array(keyPath.dropLast()))
  40. (subDict as? Value).map { self[keyPath.last!] = $0 }
  41. }
  42. }
  43. }