Skip to content

Instantly share code, notes, and snippets.

@SergLam
Last active March 17, 2020 06:01
Show Gist options
  • Save SergLam/56abc90d04241b45d0be14bc14d2ca8c to your computer and use it in GitHub Desktop.
Save SergLam/56abc90d04241b45d0be14bc14d2ca8c to your computer and use it in GitHub Desktop.
Swift - Codable - magic parsing, ignoring JS types issues
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)
}
}
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