I have the following data structure:
{
"type": "foo"
"data": { /* foo object */ }
}
Here's my class for decoding it:
final public class UntypedObject: Decodable {
public var data: Data
enum UntypedObjectKeys: CodingKey {
case data
}
required public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: UntypedObjectKeys.self)
self.data = try values.decode(Data.self, forKey: .data)
}
}
I am fetching an array of such objects and this is how I am decoding it:
let decoder = JSONDecoder()
let objectList = try decoder.decode([UntypedObject].self, from: data)
However I am receiving this error in the console:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)), Playground_Sources.UntypedObject.UntypedObjectKeys.data], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
So the question would be is it possible at all to decode proper JSON object into a Data
typed attribute and if so - how can I achieve this?
This is likely too late for the OP, but I had a similar problem that needed a solution. Hopefully this is useful for others.
For my problem, I wanted to use Decodable
to decode my JSON
with Swift 5.1. However at various points in my object hierarchy, I wanted to return an objC object (from a third party library) that did not support Decodable
, but did support decoding from a (non-trivial) JSON string. I solved the problem by using JSONSerialization
to create an untyped object hierarchy I could retrieve from the decoder's userInfo
property and search with the decoder's contextPath
to find my data, and then use JSONSerialization
to
convert it back to string data.
This solution makes no assumption about the object/array hierarchy required to get to the object that has the "data" key.
// Swift 5.1 Playground
import Foundation
// Input configuration JSON
let jsonStr = """
{
"foo":"bar",
"bars": [
{
"data":{
"thing1":"#111100",
"thing2":12
}
},
{
"data":{
"thing1":"#000011",
"thing2":64.125
}
}
]
}
"""
// Object passed to the decoder in the UserInfo Dictionary
// This will contain the serialized JSON data for use by
// child objects
struct MyCodingOptions {
let json: Any
static let key = CodingUserInfoKey(rawValue: "com.unique.mycodingoptions")!
}
let jsonData = Data(jsonStr.utf8)
let json = try JSONSerialization.jsonObject(with: jsonData)
let options = MyCodingOptions(json: json)
let decoder = JSONDecoder()
decoder.userInfo = [MyCodingOptions.key: options]
// My object hierarchy
struct Root: Decodable {
let foo: String
let bars: [Bar]
}
struct Bar: Decodable {
let data: Data?
enum CodingKeys: String, CodingKey {
case data = "data"
}
}
// Implement a custom decoder for Bar
// Use the context path and the serialized JSON to get the json value
// of "data" and then deserialize it back to data.
extension Bar {
init(from decoder: Decoder) throws {
var data: Data? = nil
if let options = decoder.userInfo[MyCodingOptions.key] as? MyCodingOptions {
// intialize item to the whole json object, then mutate it down the "path" to Bar
var item: Any? = options.json
let path = decoder.codingPath // The path to the current object, does not include self
for key in path {
if let intKey = key.intValue {
//array
item = (item as? [Any])?[intKey]
} else {
//object
item = (item as? [String:Any])?[key.stringValue]
}
}
// item is now Bar, which is an object (Dictionary)
let bar = item as? [String:Any]
let dataKey = CodingKeys.data.rawValue
if let item = bar?[dataKey] {
data = try JSONSerialization.data(withJSONObject: item)
}
}
self.init(data: data)
}
}
if let root = try? decoder.decode(Root.self, from: jsonData) {
print("foo: \(root.foo)")
for (i, bar) in root.bars.enumerated() {
if let data = bar.data {
print("data #\(i): \(String(decoding: data, as: UTF8.self))")
}
}
}
//prints:
// foo: bar
// data #0: {"thing2":12,"thing1":"#111100"}
// data #1: {"thing2":64.125,"thing1":"#000011"}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With