Last active
March 17, 2020 06:01
-
-
Save SergLam/56abc90d04241b45d0be14bc14d2ca8c to your computer and use it in GitHub Desktop.
Swift - Codable - magic parsing, ignoring JS types issues
This file contains hidden or 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 | |
enum CodingError: LocalizedError, Error { | |
case unableToDecode(key: String, type: String) | |
var errorDescription: String? { | |
switch self { | |
case .unableToDecode(let key, let type): | |
return Localizable.errorParsingKey(key, type) | |
} | |
} | |
} | |
struct JSONCodingKeys: CodingKey { | |
var stringValue: String | |
init?(stringValue: String) { | |
self.stringValue = stringValue | |
} | |
var intValue: Int? | |
init?(intValue: Int) { | |
self.init(stringValue: "\(intValue)") | |
self.intValue = intValue | |
} | |
} | |
extension KeyedDecodingContainer { | |
func decode(_ type: [String: Any].Type, forKey key: K) throws -> [String: Any] { | |
let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key) | |
return try container.decode(type) | |
} | |
func decodeIfPresent(_ type: [String: Any].Type, forKey key: K) throws -> [String: Any]? { | |
guard contains(key) else { | |
return nil | |
} | |
guard try decodeNil(forKey: key) == false else { | |
return nil | |
} | |
return try decode(type, forKey: key) | |
} | |
func decode(_ type: [Any].Type, forKey key: K) throws -> [Any] { | |
var container = try self.nestedUnkeyedContainer(forKey: key) | |
return try container.decode(type) | |
} | |
func decodeIfPresent(_ type: [Any].Type, forKey key: K) throws -> [Any]? { | |
guard contains(key) else { | |
return nil | |
} | |
guard try decodeNil(forKey: key) == false else { | |
return nil | |
} | |
return try decode(type, forKey: key) | |
} | |
func decode(_ type: [String: Any].Type) throws -> [String: Any] { | |
var dictionary = [String: Any]() | |
for key in allKeys { | |
if let boolValue = try? decode(Bool.self, forKey: key) { | |
dictionary[key.stringValue] = boolValue | |
} else if let stringValue = try? decode(String.self, forKey: key) { | |
dictionary[key.stringValue] = stringValue | |
} else if let intValue = try? decode(Int.self, forKey: key) { | |
dictionary[key.stringValue] = intValue | |
} else if let doubleValue = try? decode(Double.self, forKey: key) { | |
dictionary[key.stringValue] = doubleValue | |
} else if let nestedDictionary = try? decode([String: Any].self, forKey: key) { | |
dictionary[key.stringValue] = nestedDictionary | |
} else if let nestedArray = try? decode([Any].self, forKey: key) { | |
dictionary[key.stringValue] = nestedArray | |
} | |
} | |
return dictionary | |
} | |
} | |
extension UnkeyedDecodingContainer { | |
mutating func decode(_ type: [Any].Type) throws -> [Any] { | |
var array: [Any] = [] | |
while isAtEnd == false { | |
// See if the current value in the JSON array is `null` first and prevent infite recursion with nested arrays. | |
if try decodeNil() { | |
continue | |
} else if let value = try? decode(Bool.self) { | |
array.append(value) | |
} else if let value = try? decode(Double.self) { | |
array.append(value) | |
} else if let value = try? decode(String.self) { | |
array.append(value) | |
} else if let nestedDictionary = try? decode([String: Any].self) { | |
array.append(nestedDictionary) | |
} else if let nestedArray = try? decode([Any].self) { | |
array.append(nestedArray) | |
} | |
} | |
return array | |
} | |
mutating func decode(_ type: [String: Any].Type) throws -> [String: Any] { | |
let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self) | |
return try nestedContainer.decode(type) | |
} | |
} |
This file contains hidden or 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 | |
extension KeyedDecodingContainer { | |
/** | |
Use to decode Int values using JS logic ["0", "1", 0, 1] | |
*/ | |
func decodeForce(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int { | |
let error = CodingError.unableToDecode(key: key.stringValue, type: String(describing: Int.self)) | |
// First try to decode it as a regular Int | |
let intValue = try? self.decode(Int.self, forKey: key) | |
if let int = intValue { | |
return int | |
} | |
// Then try to decode it as string value | |
let stringValue = try? self.decode(String.self, forKey: key) | |
// Check all the above values | |
if let string = stringValue, let intFromStr = Int(string) { | |
return intFromStr | |
// In case of empty string return zero value | |
} else if let string = stringValue, string.isEmpty { | |
return 0 | |
} else { | |
throw error | |
} | |
} | |
func decodeForce(_ type: Date.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Date { | |
let error = CodingError.unableToDecode(key: key.stringValue, type: String(describing: Date.self)) | |
// First try to decode it as a regular Int | |
let value = try? self.decode(Date.self, forKey: key) | |
if let date = value { | |
return date | |
} else { | |
throw error | |
} | |
} | |
func decodeForce(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String { | |
let error = CodingError.unableToDecode(key: key.stringValue, type: String(describing: String.self)) | |
// First try to decode it as a regular Int | |
let value = try? self.decode(String.self, forKey: key) | |
if let str = value { | |
return str | |
} else { | |
throw error | |
} | |
} | |
func decodeForce(_ type: Double.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Double { | |
let error = CodingError.unableToDecode(key: key.stringValue, type: String(describing: Double.self)) | |
// First try to decode it as a regular Int | |
let doubleValue = try? self.decode(Double.self, forKey: key) | |
if let double = doubleValue { | |
return double | |
} | |
// Then try to decode it as string value | |
let stringValue = try? self.decode(String.self, forKey: key) | |
// Check all the above values | |
if let string = stringValue, let intFromStr = Double(string) { | |
return intFromStr | |
// In case of empty string return zero value | |
} else if let string = stringValue, string.isEmpty { | |
return 0 | |
} else { | |
throw error | |
} | |
} | |
/** | |
Use to decode Bool values using JS logic ["true", "false", "0", "1", 0, 1, true, false] | |
*/ | |
func decodeForce(_ type: Bool.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool { | |
let error = CodingError.unableToDecode(key: key.stringValue, type: String(describing: Bool.self)) | |
// First try to decode it as a regular Bool - true, false | |
let boolValue = try? self.decode(Bool.self, forKey: key) | |
if let bool = boolValue { | |
return bool | |
} | |
// Then try to decode it as int value - 0, 1 | |
let intValue = try? self.decode(Int.self, forKey: key) | |
// Then try to decode it as string value - "true", "false", "0", "1", | |
let stringValue = try? self.decode(String.self, forKey: key) | |
// Check all the above values | |
if let int = intValue, int == 0 || int == 1 { | |
return int == 1 | |
} else if let string = stringValue { | |
// "true", "false", "0", "1", | |
if string.count == 1 { | |
// "0", "1" | |
guard let intFromStr = Int(string) else { | |
throw error | |
} | |
return intFromStr == 1 | |
} else { | |
switch string { | |
case "true": | |
return true | |
case "false": | |
return false | |
default: | |
throw error | |
} | |
} | |
} else { | |
throw error | |
} | |
} | |
func decodeIfPresentForce(_ type: Bool.Type, forKey key: K) throws -> Bool? { | |
guard contains(key) else { | |
return nil | |
} | |
guard try decodeNil(forKey: key) == false else { | |
return nil | |
} | |
return try decodeForce(type, forKey: key) | |
} | |
func decodeIfPresentForce(_ type: Int.Type, forKey key: K) throws -> Int? { | |
guard contains(key) else { | |
return nil | |
} | |
guard try decodeNil(forKey: key) == false else { | |
return nil | |
} | |
return try decodeForce(type, forKey: key) | |
} | |
func decodeIfPresentForce(_ type: Double.Type, forKey key: K) throws -> Double? { | |
guard contains(key) else { | |
return nil | |
} | |
guard try decodeNil(forKey: key) == false else { | |
return nil | |
} | |
return try decodeForce(type, forKey: key) | |
} | |
func decodeIfPresentForce(_ type: String.Type, forKey key: K) throws -> String? { | |
guard contains(key) else { | |
return nil | |
} | |
guard try decodeNil(forKey: key) == false else { | |
return nil | |
} | |
return try decodeForce(type, forKey: key) | |
} | |
func decodeIfPresentForce(_ type: Date.Type, forKey key: K) throws -> Date? { | |
guard contains(key) else { | |
return nil | |
} | |
guard try decodeNil(forKey: key) == false else { | |
return nil | |
} | |
return try decodeForce(type, forKey: key) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment