Last active
March 14, 2024 18:28
-
-
Save groue/598ffd92103fbcf29b26 to your computer and use it in GitHub Desktop.
Swift recursive walking of JSON-like structure: values (any Swift type, any ObjC class), array of values, dictionaries of values.
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 | |
// ============================================================================= | |
// Box | |
// The Box | |
struct Box { | |
let value: Any | |
let walk: () -> () | |
} | |
// ============================================================================= | |
// Boxing simple Swift types | |
// Boxable object can produce a Box | |
protocol Boxable { | |
var mustacheBox: Box { get } | |
} | |
// box(Boxable) -> Box | |
func box<T: Boxable>(boxable: T) -> Box { | |
return boxable.mustacheBox | |
} | |
// box(Int) | |
extension Int: Boxable { | |
var mustacheBox: Box { | |
return Box( | |
value: self, | |
walk: { print("Int(\(self))") } | |
) | |
} | |
} | |
let boxInt = box(1) | |
println("\nboxInt") | |
boxInt.walk() | |
println() | |
let boxedInt = boxInt.value as Int | |
// box(Double) | |
extension Double: Boxable { | |
var mustacheBox: Box { | |
return Box( | |
value: self, | |
walk: { print("Double(\(self))") } | |
) | |
} | |
} | |
let boxDouble = box(1.0) | |
println("\nboxDouble") | |
boxDouble.walk() | |
println() | |
let boxedDouble = boxDouble.value as Double | |
// box(String) | |
extension String: Boxable { | |
var mustacheBox: Box { | |
return Box( | |
value: self, | |
walk: { print("String(\(self))") } | |
) | |
} | |
} | |
let boxString = box("foo") | |
println("\nboxString") | |
boxString.walk() | |
println() | |
let boxedString = boxString.value as String | |
// box(Bool) | |
extension Bool: Boxable { | |
var mustacheBox: Box { | |
return Box( | |
value: self, | |
walk: { print("Bool(\(self))") } | |
) | |
} | |
} | |
let boxBool = box(true) | |
println("\nboxBool") | |
boxBool.walk() | |
println() | |
let boxedBool = boxBool.value as Bool | |
// ============================================================================= | |
// Boxing Swift arrays | |
// box([Boxable]) -> Box | |
func box<S: SequenceType where S.Generator.Element: Boxable>(sequence: S) -> Box { | |
return Box( | |
value: map(sequence) { $0.mustacheBox }, | |
walk: { | |
print("[") | |
for boxable in sequence { | |
boxable.mustacheBox.walk() | |
print(",") | |
} | |
print("]") | |
} | |
) | |
} | |
let arrayOfBoxable = [1,2,3] | |
let boxArrayOfBoxable = box(arrayOfBoxable) | |
println("\nboxArrayOfBoxable") | |
boxArrayOfBoxable.walk() | |
println() | |
let boxedArrayOfBoxable = boxArrayOfBoxable.value as [Box] // TODO: keep original value | |
boxedArrayOfBoxable[0].value as Int | |
extension Box: Boxable { | |
var mustacheBox: Box { | |
return self | |
} | |
} | |
let arrayOfBox = [box(1), box("foo"), box(true)] | |
let boxArrayOfBox = box(arrayOfBox) | |
println("\nboxArrayOfBox") | |
boxArrayOfBox.walk() | |
println() | |
let boxedArrayOfBox = boxArrayOfBox.value as [Box] | |
boxedArrayOfBox[0].value as Int | |
// ============================================================================= | |
// Boxing Swift dictionaries | |
// box([String: Boxable]) -> Box | |
func box<T: Boxable>(dictionary: [String: T]) -> Box { | |
var boxedDictionary: [String: Box] = [:] | |
for (key, value) in dictionary { | |
boxedDictionary[key] = box(value) | |
} | |
return Box( | |
value: boxedDictionary, | |
walk: { | |
print("[") | |
for (key, boxable) in dictionary { | |
print("\(key):") | |
boxable.mustacheBox.walk() | |
print(",") | |
} | |
print("]") | |
} | |
) | |
} | |
let dictOfBoxable = ["toto": 1] | |
let boxDictOfBoxable = box(dictOfBoxable) | |
println("\ndictOfBoxable") | |
boxDictOfBoxable.walk() | |
println() | |
let boxedDictOfBoxable = boxDictOfBoxable.value as [String: Box] // TODO: keep original value | |
boxedDictOfBoxable["toto"]!.value as Int | |
let dictOfBox = [ | |
"name": box("Chris"), | |
"value": box(10000), | |
"taxed_value": box(10000 - (10000 * 0.4)), | |
"in_ca": box(true) | |
] | |
let boxDictOfBox = box(dictOfBox) | |
println("\nboxDictOfBox") | |
boxDictOfBox.walk() | |
println() | |
let boxedDictOfBox = boxDictOfBox.value as [String: Box] | |
boxedDictOfBox["name"]!.value as String | |
// ============================================================================= | |
// Boxing Objctive-C objects | |
// The Boxable protocol can not be used by Objc classes, because the Box struct | |
// is not compatible with ObjC. So let's define another protocol. | |
@objc protocol ObjCBoxable { | |
// Can not return a Box, because Box is not compatible with ObjC. | |
// So let's return an ObjC object which wraps a Box. | |
var mustacheBoxWrapper: ObjCBoxWrapper { get } | |
} | |
// The ObjC object which wraps a Box (see ObjCBoxable) | |
class ObjCBoxWrapper: NSObject { | |
let box: Box | |
init(box: Box) { | |
self.box = box | |
} | |
} | |
// box(ObjCBoxable) -> Box | |
func box(boxable: ObjCBoxable) -> Box { | |
return boxable.mustacheBoxWrapper.box | |
} | |
// box(NSNumber) | |
extension NSNumber: ObjCBoxable { | |
var mustacheBoxWrapper: ObjCBoxWrapper { | |
let objCType = self.objCType | |
let str = String.fromCString(objCType) | |
switch str! { | |
case "c", "i", "s", "l", "q", "C", "I", "S", "L", "Q": | |
return ObjCBoxWrapper(box: box(Int(longLongValue))) | |
case "f", "d": | |
return ObjCBoxWrapper(box: box(doubleValue)) | |
case "B": | |
return ObjCBoxWrapper(box: box(boolValue)) | |
default: | |
fatalError("Not implemented yet") | |
} | |
} | |
} | |
let NSNum = NSNumber(int: 1) | |
let boxNSNum = box(NSNum) | |
println("\nboxNSNum") | |
boxNSNum.walk() | |
println() | |
let boxedNSNum = boxNSNum.value as Int // TODO: keep original value | |
// box(NSString) | |
extension NSString: ObjCBoxable { | |
var mustacheBoxWrapper: ObjCBoxWrapper { | |
return ObjCBoxWrapper(box: box(self as String)) | |
} | |
} | |
let NSStr: NSString = "foo" | |
let boxNSStr = box(NSStr) | |
println("\nboxNSStr") | |
boxNSStr.walk() | |
println() | |
let boxedNSStr = boxNSStr.value as String // TODO: keep original value | |
// box(NSDictionary) | |
extension NSDictionary: ObjCBoxable { | |
var mustacheBoxWrapper: ObjCBoxWrapper { | |
var boxedDictionary: [String: Box] = [:] | |
for (key, value) in self { | |
if let key = key as? String { | |
if let objCBoxable = value as? ObjCBoxable { | |
boxedDictionary[key] = objCBoxable.mustacheBoxWrapper.box | |
} | |
} | |
} | |
return ObjCBoxWrapper(box: box(boxedDictionary)) | |
} | |
} | |
let NSDict = [ | |
"name": "Chris", | |
"value": 10000, | |
"taxed_value": 10000 - (10000 * 0.4), | |
"in_ca": true | |
] | |
let boxNSDict = box(NSDict) | |
println("\nboxNSDict") | |
boxNSDict.walk() | |
println() | |
let boxedNSDict = boxNSDict.value as [String: Box] // TODO: keep original value | |
// box(NSArray) | |
extension NSArray: ObjCBoxable { | |
var mustacheBoxWrapper: ObjCBoxWrapper { | |
var boxedArray: [Box] = [] | |
for value in self { | |
if let objCBoxable = value as? ObjCBoxable { | |
boxedArray.append(objCBoxable.mustacheBoxWrapper.box) | |
} | |
} | |
return ObjCBoxWrapper(box: box(boxedArray)) | |
} | |
} | |
let NSArr = [1, "foo", true] | |
let boxNSArr = box(NSArr) | |
println("\nboxNSArr") | |
boxNSArr.walk() | |
println() | |
let boxedNSArr = boxNSArr.value as [Box] // TODO: keep original value | |
// ============================================================================= | |
// Boxing Recursive values | |
let recursiveValue = [ | |
"a": [1,2,3], | |
"b": [4,5,6] | |
] | |
let boxRecursiveValue = box(recursiveValue) | |
println("\nboxRecursiveValue") | |
boxRecursiveValue.walk() | |
println() | |
let boxedRecursiveValue = boxRecursiveValue.value as [String: Box] | |
let aBox = boxedRecursiveValue["a"]! | |
let aBoxedInts = aBox.value as [Box] | |
let aInt = aBoxedInts[0].value as Int | |
let bBox = boxedRecursiveValue["b"]! | |
let bBoxedInts = bBox.value as [Box] | |
let bInt = bBoxedInts[0].value as Int | |
let deepRecursiveValue = [[[1,2,3]]] | |
let boxDeepRecursiveValue = box(deepRecursiveValue) | |
println("\nboxDeepRecursiveValue") | |
boxDeepRecursiveValue.walk() | |
println() | |
let deepBox1 = boxDeepRecursiveValue.value as [Box] | |
let deepBox2 = deepBox1[0].value as [Box] | |
let deepBox3 = deepBox2[0].value as [Box] | |
let deepInt = deepBox3[0].value as Int | |
println("\nEND") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment