Skip to content

Instantly share code, notes, and snippets.

@mikebuss
Created January 25, 2019 21:46
Show Gist options
  • Save mikebuss/17142624da4baf9cdcc337861e256533 to your computer and use it in GitHub Desktop.
Save mikebuss/17142624da4baf9cdcc337861e256533 to your computer and use it in GitHub Desktop.

Revisions

  1. 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)
    }
    }