Skip to content

Instantly share code, notes, and snippets.

@groue
Last active March 14, 2024 18:28
Show Gist options
  • Save groue/598ffd92103fbcf29b26 to your computer and use it in GitHub Desktop.
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.
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