Skip to content

Instantly share code, notes, and snippets.

@tail-call
Last active June 19, 2023 15:03
Show Gist options
  • Save tail-call/94ca29a100d9462f855faa739d9b86ef to your computer and use it in GitHub Desktop.
Save tail-call/94ca29a100d9462f855faa739d9b86ef to your computer and use it in GitHub Desktop.
I want to encode/decode [String: Any] that badly...
//
// JSONWrapper.swift
//
// Created by Maria Zaitseva on 16.03.2021.
//
import Foundation
/// This class provides a straightforward way to encode/decode
/// objects of type `[String: Any]` to and from JSON.
indirect enum JSONWrapper {
case string(String)
case int(Int)
case double(Double)
case boolean(Bool)
case object([String: JSONWrapper])
case array([JSONWrapper])
enum JSONWrapperError: Error {
case invalidValue(Any)
case failedToDecode(from: Decoder)
}
init(_ value: Any) throws {
if let string = value as? String {
self = .string(string)
return
} else if let int = value as? Int {
self = .int(int)
return
} else if let double = value as? Double {
self = .double(double)
return
} else if let boolean = value as? Bool {
self = .boolean(boolean)
return
} else if let object = value as? [String: Any] {
let wrapperObject = try object.mapValues { item -> JSONWrapper in
return try JSONWrapper(item)
}
self = .object(wrapperObject)
return
} else if let array = value as? [Any] {
let wrapperArray = try array.map { item in
return try JSONWrapper(item)
}
self = .array(wrapperArray)
return
}
throw JSONWrapperError.invalidValue(value)
}
func unwrap() -> Any {
switch self {
case .string(let string):
return string
case .int(let int):
return int
case .double(let double):
return double
case .boolean(let boolean):
return boolean
case .object(let object):
return object.mapValues { value in
return value.unwrap()
}
case .array(let array):
return array.map { value in
return value.unwrap()
}
}
}
}
extension JSONWrapper: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let string):
try container.encode(string)
case .int(let int):
try container.encode(int)
case .double(let double):
try container.encode(double)
case .boolean(let boolean):
try container.encode(boolean)
case .object(let object):
try container.encode(object)
case .array(let array):
try container.encode(array)
}
}
}
extension JSONWrapper: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
self = .string(string)
return
} else if let int = try? container.decode(Int.self) {
self = .int(int)
return
} else if let double = try? container.decode(Double.self) {
self = .double(double)
return
} else if let boolean = try? container.decode(Bool.self) {
self = .boolean(boolean)
return
} else if let array = try? container.decode([JSONWrapper].self) {
self = .array(array)
return
} else if let object = try? container.decode([String: JSONWrapper].self) {
self = .object(object)
return
}
throw JSONWrapperError.failedToDecode(from: decoder)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment