Created
April 4, 2019 18:55
-
-
Save YannisDC/f21a7c7ceb03363b799b881b8b82ac72 to your computer and use it in GitHub Desktop.
This works but not sure how fragile
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 Cocoa | |
fileprivate extension CodingUserInfoKey { | |
static let fragmentBoxedType = CodingUserInfoKey( | |
rawValue: "CodingUserInfoKey.fragmentBoxedType" | |
)! | |
} | |
extension JSONDecoder { | |
private struct FragmentDecodingBox<T : Decodable> : Decodable { | |
var value: T | |
init(from decoder: Decoder) throws { | |
let type = decoder.userInfo[.fragmentBoxedType] as! T.Type | |
var container = try decoder.unkeyedContainer() | |
self.value = try container.decode(type) | |
} | |
} | |
private func copy() -> JSONDecoder { | |
let decoder = JSONDecoder() | |
decoder.dataDecodingStrategy = dataDecodingStrategy | |
decoder.dateDecodingStrategy = dateDecodingStrategy | |
decoder.keyDecodingStrategy = keyDecodingStrategy | |
decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy | |
decoder.userInfo = userInfo | |
return decoder | |
} | |
public func decode<T : Decodable>( | |
_ type: T.Type, from data: Data, allowFragments: Bool | |
) throws -> T { | |
// If we're not allowing fragments, just delegate to decode(_:from:). | |
guard allowFragments else { return try decode(type, from: data) } | |
if "\(T.self)" == "String" { return String(data: data, encoding: .utf8) as! T } | |
// Box the JSON object in an array so we can pass it off to JSONDecoder. | |
// The round-tripping through JSONSerialization isn't ideal, but it | |
// ensures we do The Right Thing regardless of the encoding of `data`. | |
// | |
// FIX-ME: Try to detect encoding and add the '[]' delimeters directly to | |
// the data without going through `JSONSerialization`. | |
let jsonObject = try JSONSerialization | |
.jsonObject(with: data, options: .allowFragments) | |
let boxedData = try JSONSerialization.data(withJSONObject: [jsonObject]) | |
// Copy the decoder so we can mutate the userInfo without having to worry | |
// about data races. | |
let decoder = copy() | |
decoder.userInfo[.fragmentBoxedType] = type | |
// Use FragmentDecodingBox to decode the underlying fragment from the | |
// array. | |
// | |
// We're intentionally *not* doing `decode([T].self, ...)` here, as | |
// that loses the dynamic type passed – breaking things like: | |
// | |
// class C : Decodable {} | |
// class D {} | |
// | |
// let type: C.Type = D.self | |
// let data = ... | |
// let decoded = try JSONDecoder().decode(type, from: data, allowFragments: true) | |
// | |
// The above would decode a `C` instead of a `D` if we didn't preserve | |
// the dynamic type. | |
// | |
// (Admittedly this is a bit of contrived example, as by default such types | |
// would decode using keyed containers and therefore not be fragments – | |
// nontheless it is possible for them to implement their decoding such that | |
// they use a single value container). | |
return try decoder.decode(FragmentDecodingBox<T>.self, from: boxedData).value | |
} | |
} | |
struct Person: Codable { | |
let name: String | |
let age: Int | |
let friends: [Person]? | |
} | |
let person = Person(name: "Brad", age: 53, friends: nil) | |
protocol Identifiable { | |
var uuid: String { get set } | |
} | |
protocol Encryptable { | |
var encrypted: Bool { get } | |
func encrypt() | |
func decrypt() | |
} | |
public class EncryptableProperty<T: Codable>: Encryptable { | |
var value: T | |
var encryptedValue: String | |
var encrypted: Bool | |
init(value: T, encrypted: Bool) { | |
self.value = value | |
self.encrypted = encrypted | |
self.encryptedValue = "" | |
} | |
func encrypt() { | |
if value is LosslessStringConvertible, let value = value as? LosslessStringConvertible { | |
self.encryptedValue = value.description | |
} else { | |
do { | |
let object = try JSONEncoder().encode(value) | |
// Do actual encryption and get the signature back | |
if let string = String(data: object, encoding: .utf8) { | |
print(string) | |
encryptedValue = string | |
} else { | |
print("not a valid UTF-8 sequence") | |
} | |
} catch{ | |
print("Encoding failed") | |
} | |
} | |
} | |
func decrypt() { | |
do { | |
let data : [UInt8] = [UInt8](self.encryptedValue.utf8) | |
// Do actual decryption and get the data back | |
let object = try JSONDecoder().decode(T.self, from: Data(data), allowFragments: true) | |
self.value = object | |
} catch{ | |
print("Decoding failed") | |
} | |
} | |
} | |
protocol EncryptableObject { | |
func encryptObject() | |
func decryptObject() | |
} | |
struct Test: EncryptableObject, Identifiable { | |
public let testInt: EncryptableProperty<Array<String>> | |
var uuid: String | |
init(uuid: String = UUID().uuidString) { | |
self.testInt = EncryptableProperty<Array<String>>(value: ["one", "two"], encrypted: true) | |
self.uuid = uuid | |
} | |
func encryptObject() { | |
print("\nEncryption happens") | |
Mirror(reflecting: self) | |
.children | |
.forEach { (property) in | |
if let encryptableValue = property.value as? Encryptable { | |
encryptableValue.encrypt() | |
} | |
} | |
} | |
func decryptObject() { | |
print("\nDecryption happens") | |
Mirror(reflecting: self) | |
.children | |
.forEach { (property) in | |
if let encryptableValue = property.value as? Encryptable { | |
encryptableValue.decrypt() | |
} | |
} | |
} | |
} | |
let test = Test() | |
print("Value= \(test.testInt.value) EncryptedValue= \(test.testInt.encryptedValue)") | |
test.encryptObject() | |
print("Value= \(test.testInt.value) EncryptedValue= \(test.testInt.encryptedValue)") | |
print("\nDecrypted value gets updated") | |
test.testInt.encryptedValue = #"["sir", "yannis"]"# | |
print("Value= \(test.testInt.value) EncryptedValue= \(test.testInt.encryptedValue)") | |
test.decryptObject() | |
print("Value= \(test.testInt.value) EncryptedValue= \(test.testInt.encryptedValue)") | |
struct Test2: EncryptableObject, Identifiable { | |
public let testString: EncryptableProperty<String> | |
public let testInt: EncryptableProperty<String> | |
var uuid: String | |
init(uuid: String = UUID().uuidString) { | |
self.testString = EncryptableProperty<String>(value: "Blockstack", encrypted: true) | |
self.testInt = EncryptableProperty<String>(value: "Blockstack", encrypted: true) | |
self.uuid = uuid | |
} | |
func encryptObject() { | |
print("\nEncryption happens") | |
Mirror(reflecting: self) | |
.children | |
.forEach { (property) in | |
if let encryptableValue = property.value as? Encryptable { | |
encryptableValue.encrypt() | |
} | |
} | |
} | |
func decryptObject() { | |
print("\nDecryption happens") | |
Mirror(reflecting: self) | |
.children | |
.forEach { (property) in | |
if let encryptableValue = property.value as? Encryptable { | |
encryptableValue.decrypt() | |
} | |
} | |
} | |
} | |
let test2 = Test2() | |
print("Value= \(test2.testInt.value) EncryptedValue= \(test2.testInt.encryptedValue)") | |
test2.encryptObject() | |
print("Value= \(test2.testInt.value) EncryptedValue= \(test2.testInt.encryptedValue)") | |
print("\nDecrypted value gets updated") | |
test2.testInt.encryptedValue = "Test" | |
print("Value= \(test2.testInt.value) EncryptedValue= \(test2.testInt.encryptedValue)") | |
test2.decryptObject() | |
print("Value= \(test2.testInt.value) EncryptedValue= \(test2.testInt.encryptedValue)") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment