Skip to content

Instantly share code, notes, and snippets.

@chosa91
Last active April 20, 2021 08:07
Show Gist options
  • Save chosa91/5d3a13c4448972a199d4893f34832ec7 to your computer and use it in GitHub Desktop.
Save chosa91/5d3a13c4448972a199d4893f34832ec7 to your computer and use it in GitHub Desktop.
Destructure dictionaries into tuple
// Source: https://twitter.com/NSExceptional/status/1383665312684335105
// MARK: - Helpers
extension UnsafePointer {
var raw: UnsafeRawPointer {
return UnsafeRawPointer(self)
}
var mutable: UnsafeMutablePointer<Pointee> {
return UnsafeMutablePointer<Pointee>(mutating: self)
}
func buffer(n: Int) -> UnsafeBufferPointer<Pointee> {
return UnsafeBufferPointer(start: self, count: n)
}
}
struct Vector<Element> {
var element: Element
mutating func vector(n: Int) -> UnsafeBufferPointer<Element> {
return withUnsafePointer(to: &self) {
$0.withMemoryRebound(to: Element.self, capacity: 1) { start in
return start.buffer(n: n)
}
}
}
mutating func element(at i: Int) -> UnsafeMutablePointer<Element> {
return withUnsafePointer(to: &self) {
return $0.raw.assumingMemoryBound(to: Element.self).advanced(by: i).mutable
}
}
}
struct TupleMetadataLayout {
var _kind: Int
var numberOfElements: Int
var labelsString: UnsafeMutablePointer<CChar>
var elementVector: Vector<TupleElementLayout>
}
struct TupleElementLayout {
var type: Any.Type
var offset: Int
}
struct TupleMetadata {
let pointer: UnsafeMutablePointer<TupleMetadataLayout>
init(type: Any.Type) {
pointer = unsafeBitCast(type, to: UnsafeMutablePointer<TupleMetadataLayout>.self)
}
var type: Any.Type {
return unsafeBitCast(pointer, to: Any.Type.self)
}
func numberOfElements() -> Int {
return pointer.pointee.numberOfElements
}
func labels() -> [String] {
guard Int(bitPattern: pointer.pointee.labelsString) != 0 else {
return (0..<numberOfElements()).map { _ in "" }
}
var labels = String(cString: pointer.pointee.labelsString).components(separatedBy: " ")
labels.removeLast()
return labels
}
func elements() -> UnsafeBufferPointer<TupleElementLayout> {
return pointer.pointee.elementVector.vector(n: numberOfElements())
}
func properties() -> [PropertyInfo] {
let names = labels()
let el = elements()
let num = numberOfElements()
var properties = [PropertyInfo]()
for i in 0..<num {
properties.append(
PropertyInfo(
name: names[i],
type: el[i].type,
isVar: true,
offset: el[i].offset,
ownerType: type
)
)
}
return properties
}
// mutating func toTypeInfo() -> TypeInfo {
// var info = TypeInfo(metadata: self)
// info.properties = properies()
// return info
// }
}
public struct PropertyInfo {
public let name: String
public let type: Any.Type
public let isVar: Bool
public let offset: Int
public let ownerType: Any.Type
}
// MARK: - Example
do {
let person: (name: String, age: Int) = try JSUM.decode(
from: [
"name": "Bob",
"age": 30,
]
)
dump(person)
// ▿ (2 elements)
// - name: "Bob"
// - age: 30
} catch {
print(error)
}
// MARK: - JSUM
enum JSUM {
static func decode<T>(from: [String: Any]) throws -> T {
let meta = TupleMetadata(type: T.self)
let props = meta.properties()
let mutableRawPtr = UnsafeMutableRawPointer.allocate(
byteCount: MemoryLayout<T>.size,
alignment: MemoryLayout<T>.alignment
)
defer { mutableRawPtr.deallocate() }
props.forEach { p in
let value = from[p.name]
withUnsafePointer(to: value) {
mutableRawPtr.advanced(by: p.offset).copyMemory(from: $0, byteCount: MemoryLayout.size(ofValue: value))
}
}
return mutableRawPtr.load(as: T.self)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment