Skip to content

Instantly share code, notes, and snippets.

@sukov
Forked from mikebuss/decode-json-swift.swift
Last active September 19, 2024 11:27
Show Gist options
  • Save sukov/d3834c0e7b72e4f7575f753b352f6ddd to your computer and use it in GitHub Desktop.
Save sukov/d3834c0e7b72e4f7575f753b352f6ddd to your computer and use it in GitHub Desktop.

Revisions

  1. sukov revised this gist Aug 26, 2019. No changes.
  2. sukov renamed this gist Aug 26, 2019. 1 changed file with 46 additions and 41 deletions.
    87 changes: 46 additions & 41 deletions decode-json-swift.swift → CodableExtension.swift
    Original file line number Diff line number Diff line change
    @@ -1,12 +1,7 @@
    //
    //
    // Adapted from:
    //
    // Original: https://gist.github.com/loudmouth/332e8d89d8de2c1eaf81875cfcd22e24
    // Adds encoding: https://github.com/3D4Medical/glTFSceneKit/blob/master/Sources/glTFSceneKit/GLTF/JSONCodingKeys.swift
    // Adds fix for null inside arrays causing infinite loop: https://gist.github.com/loudmouth/332e8d89d8de2c1eaf81875cfcd22e24#gistcomment-2807855
    //
    struct JSONCodingKeys: CodingKey {
    import Foundation

    private struct JSONCodingKeys: CodingKey {
    var stringValue: String

    init(stringValue: String) {
    @@ -23,12 +18,12 @@ struct JSONCodingKeys: CodingKey {

    extension KeyedDecodingContainer {
    func decode(_ type: [String: Any].Type, forKey key: K) throws -> [String: Any] {
    let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
    let container = try nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
    return try container.decode(type)
    }

    func decode(_ type: [Any].Type, forKey key: K) throws -> [Any] {
    var container = try self.nestedUnkeyedContainer(forKey: key)
    var container = try nestedUnkeyedContainer(forKey: key)
    return try container.decode(type)
    }

    @@ -38,16 +33,20 @@ extension KeyedDecodingContainer {
    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 stringValue = try? decode(String.self, forKey: key) {
    dictionary[key.stringValue] = stringValue
    } 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
    } else if let isValueNil = try? decodeNil(forKey: key), isValueNil == true {
    dictionary[key.stringValue] = nil
    } else {
    throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath + [key], debugDescription: "Unable to decode value"))
    }
    }
    return dictionary
    @@ -59,37 +58,42 @@ extension UnkeyedDecodingContainer {
    var array: [Any] = []

    while isAtEnd == false {
    let value: String? = try decode(String?.self)
    if value == nil {
    continue
    }
    if let value = try? decode(Bool.self) {
    array.append(value)
    } else if let value = try? decode(Int.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 value = try? decode(Double.self) {
    array.append(value)
    } else if let nestedDictionary = try? decode([String: Any].self) {
    array.append(nestedDictionary)
    } else if let nestedArray = try? decode([Any].self) {
    } else if let nestedArray = try? decodeNestedArray([Any].self) {
    array.append(nestedArray)
    } else if let isValueNil = try? decodeNil(), isValueNil == true {
    array.append(Optional<Any>.none as Any)
    } else {
    throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Unable to decode value"))
    }
    }
    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)
    let container = try nestedContainer(keyedBy: JSONCodingKeys.self)
    return try container.decode(type)
    }

    mutating func decodeNestedArray(_ type: [Any].Type) throws -> [Any] {
    var container = try nestedUnkeyedContainer()
    return try container.decode(type)
    }
    }


    extension KeyedEncodingContainerProtocol where Key == JSONCodingKeys {
    mutating func encode(_ value: [String: Any]) throws {
    try value.forEach({ (key, value) in
    for (key, value) in value {
    let key = JSONCodingKeys(stringValue: key)
    switch value {
    case let value as Bool:
    @@ -100,8 +104,6 @@ extension KeyedEncodingContainerProtocol where Key == JSONCodingKeys {
    try encode(value, forKey: key)
    case let value as Double:
    try encode(value, forKey: key)
    case let value as CGFloat:
    try encode(value, forKey: key)
    case let value as [String: Any]:
    try encode(value, forKey: key)
    case let value as [Any]:
    @@ -111,29 +113,29 @@ extension KeyedEncodingContainerProtocol where Key == JSONCodingKeys {
    default:
    throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath + [key], debugDescription: "Invalid JSON value"))
    }
    })
    }
    }
    }

    extension KeyedEncodingContainerProtocol {
    mutating func encode(_ value: [String: Any]?, forKey key: Key) throws {
    if value != nil {
    var container = self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
    try container.encode(value!)
    }
    guard let value = value else { return }

    var container = nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
    try container.encode(value)
    }

    mutating func encode(_ value: [Any]?, forKey key: Key) throws {
    if value != nil {
    var container = self.nestedUnkeyedContainer(forKey: key)
    try container.encode(value!)
    }
    guard let value = value else { return }

    var container = nestedUnkeyedContainer(forKey: key)
    try container.encode(value)
    }
    }

    extension UnkeyedEncodingContainer {
    mutating func encode(_ value: [Any]) throws {
    try value.enumerated().forEach({ (index, value) in
    for (index, value) in value.enumerated() {
    switch value {
    case let value as Bool:
    try encode(value)
    @@ -143,23 +145,26 @@ extension UnkeyedEncodingContainer {
    try encode(value)
    case let value as Double:
    try encode(value)
    case let value as CGFloat:
    try encode(value)
    case let value as [String: Any]:
    try encode(value)
    case let value as [Any]:
    try encode(value)
    try encodeNestedArray(value)
    case Optional<Any>.none:
    try encodeNil()
    default:
    let keys = JSONCodingKeys(intValue: index).map({ [ $0 ] }) ?? []
    throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath + keys, debugDescription: "Invalid JSON value"))
    }
    })
    }
    }

    mutating func encode(_ value: [String: Any]) throws {
    var nestedContainer = self.nestedContainer(keyedBy: JSONCodingKeys.self)
    try nestedContainer.encode(value)
    var container = nestedContainer(keyedBy: JSONCodingKeys.self)
    try container.encode(value)
    }
    }

    mutating func encodeNestedArray(_ value: [Any]) throws {
    var container = nestedUnkeyedContainer()
    try container.encode(value)
    }
    }
  3. @mikebuss mikebuss created this gist Jan 25, 2019.
    165 changes: 165 additions & 0 deletions decode-json-swift.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,165 @@
    //
    //
    // Adapted from:
    //
    // Original: https://gist.github.com/loudmouth/332e8d89d8de2c1eaf81875cfcd22e24
    // Adds encoding: https://github.com/3D4Medical/glTFSceneKit/blob/master/Sources/glTFSceneKit/GLTF/JSONCodingKeys.swift
    // Adds fix for null inside arrays causing infinite loop: https://gist.github.com/loudmouth/332e8d89d8de2c1eaf81875cfcd22e24#gistcomment-2807855
    //
    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 decode(_ type: [Any].Type, forKey key: K) throws -> [Any] {
    var container = try self.nestedUnkeyedContainer(forKey: key)
    return try container.decode(type)
    }

    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 {
    let value: String? = try decode(String?.self)
    if value == nil {
    continue
    }
    if let value = try? decode(Bool.self) {
    array.append(value)
    } else if let value = try? decode(Int.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)
    }
    }


    extension KeyedEncodingContainerProtocol where Key == JSONCodingKeys {
    mutating func encode(_ value: [String: Any]) throws {
    try value.forEach({ (key, value) in
    let key = JSONCodingKeys(stringValue: key)
    switch value {
    case let value as Bool:
    try encode(value, forKey: key)
    case let value as Int:
    try encode(value, forKey: key)
    case let value as String:
    try encode(value, forKey: key)
    case let value as Double:
    try encode(value, forKey: key)
    case let value as CGFloat:
    try encode(value, forKey: key)
    case let value as [String: Any]:
    try encode(value, forKey: key)
    case let value as [Any]:
    try encode(value, forKey: key)
    case Optional<Any>.none:
    try encodeNil(forKey: key)
    default:
    throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath + [key], debugDescription: "Invalid JSON value"))
    }
    })
    }
    }

    extension KeyedEncodingContainerProtocol {
    mutating func encode(_ value: [String: Any]?, forKey key: Key) throws {
    if value != nil {
    var container = self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key)
    try container.encode(value!)
    }
    }

    mutating func encode(_ value: [Any]?, forKey key: Key) throws {
    if value != nil {
    var container = self.nestedUnkeyedContainer(forKey: key)
    try container.encode(value!)
    }
    }
    }

    extension UnkeyedEncodingContainer {
    mutating func encode(_ value: [Any]) throws {
    try value.enumerated().forEach({ (index, value) in
    switch value {
    case let value as Bool:
    try encode(value)
    case let value as Int:
    try encode(value)
    case let value as String:
    try encode(value)
    case let value as Double:
    try encode(value)
    case let value as CGFloat:
    try encode(value)
    case let value as [String: Any]:
    try encode(value)
    case let value as [Any]:
    try encode(value)
    case Optional<Any>.none:
    try encodeNil()
    default:
    let keys = JSONCodingKeys(intValue: index).map({ [ $0 ] }) ?? []
    throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath + keys, debugDescription: "Invalid JSON value"))
    }
    })
    }

    mutating func encode(_ value: [String: Any]) throws {
    var nestedContainer = self.nestedContainer(keyedBy: JSONCodingKeys.self)
    try nestedContainer.encode(value)
    }
    }