可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
{
"values":[
[1,1,7,"Azuan Child","Anak Azuan","12345","ACTIVE","Morning",7,12,"2017-11-09 19:45:00"],
[28,1,0,"Azuan Child2","Amran","123456","ACTIVE","Evening",1,29,"2017-11-09 19:45:00"]
]
}
Ok, this is my json format that i received from the server
Right now i want to decode it into my struct but still have no luck on it.
struct ChildrenTable: Decodable {
var values: [[String]]?
}
And my caller method on URLSession look like this
URLSession.shared.dataTask(with: request) { (data, response, err) in
guard let data = data else { return }
let dataAsString = String(data: data, encoding: .utf8)
print(dataAsString)
do {
let children = try
JSONDecoder().decode(ChildrenTable.self, from: data)
print (children)
} catch let jsonErr {
print ("Error serializing json: ", jsonErr)
}
}.resume()
And the error that i got are
Error serializing json:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [Vito_Parent.ChildrenTable.(CodingKeys in _1B826CD7D9609504747BED0EC0B7D3B5).values, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)),
Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0))],
debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
I know there's an int in the array and i only cast String for the values var values: [[String]]?
( the reason why this error popup), but i simply cannot use any multidimensional array or tuples in my structs since it follow the protocol of Decodable.
I also cannot convert the data into dictionary since it will throw error "Expected to decode Dictionary but found array instead"
Any ideas on solving this problem? i tried casting string type on data but still no luck...
p/s: if all the json format are in string type, there would be no problem, but i dont have the permission on changing that since i call it from API.
回答1:
As you said, your json array is multi-type but you are trying to decode all into String
. Default conformance of String
to Decodable
does not allow that. The only solution comes into my mind is to introduce new type.
struct IntegerOrString: Decodable {
var value: Any
init(from decoder: Decoder) throws {
if let int = try? Int(from: decoder) {
value = int
return
}
value = try String(from: decoder)
}
}
struct ChildrenTable: Decodable {
var values: [[IntegerOrString]]?
}
Run online
回答2:
Observe that the inner arrays in the JSON have a patterned sequence of types, and we know what that sequence is. The types in the inner arrays are in a patterned sequence: 3 Ints, 5 Strings, 2 Ints, and something probably intended as a Date. Clearly, in the minds of the JSON designers, each of these 11 elements has a fixed and known meaning.
This means that we can pick up the 11 elements one by one, manually, by dumpster-diving and decoding the entire JSON expression manually.
The arrays have mixed types, and Swift doesn't like that, so we will have to express them as an array of Any (or of AnyObject); but we can obtain them as themselves, rather than having to wrap them up in an artificial intermediate struct.
By the way, if you know what the meaning of each element is, then instead of an array of Any, you can decode the inner array into a struct with 11 named properties that express what each element signifies. That would be a cleaner result, but I have not used it because I do not know the meanings of the 11 values.
Here we go:
struct S : Decodable {
var values : [[Any]]
enum CodingKeys : String, CodingKey {
case values
}
init(from decoder: Decoder) throws {
// get the dictionary
let con = try! decoder.container(keyedBy: CodingKeys.self)
// get the "values" array of array
var con2 = try! con.nestedUnkeyedContainer(forKey: CodingKeys.values)
var bigarr = [[Any]]()
for _ in 0..<con2.count! {
// get a nested array
var con3 = try! con2.nestedUnkeyedContainer()
// decode all the elements of the nested array
var arr = [Any]()
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(String.self))
bigarr.append(arr)
}
// all done! finish initialization
self.values = bigarr
}
}
let result = try! JSONDecoder().decode(S.self, from: jdata)
print(result.values)
// [[1, 1, 7, "Azuan Child", "Anak Azuan", "12345", "ACTIVE",
// "Morning", 7, 12, "2017-11-09 19:45:00"],
// [28, 1, 0, "Azuan Child2", "Amran", "123456", "ACTIVE",
// "Evening", 1, 29, "2017-11-09 19:45:00"]]
回答3:
I've tried use your solution on my project, and its work like a charm. Below i've modified a bit so it can be use for single string, single array, and multidimensional array
struct TripModel: Decodable {
var tx_result: Any
var columns: [Any]
var values: [[Any]]
enum CodingKeys : String, CodingKey {
case tx_result
case columns
case values
}
init(from decoder: Decoder) throws {
var bigarr = [[Any]]()
var arrColumn = [Any]()
// get the dictionary
let con = try! decoder.container(keyedBy: CodingKeys.self)
let conResult = try! con.decode(String.self, forKey: CodingKeys.tx_result)
var conColumns = try! con.nestedUnkeyedContainer(forKey: CodingKeys.columns)
//print(String(describing: conColumns.count))
// get the "values" array of array
var con2 = try! con.nestedUnkeyedContainer(forKey: CodingKeys.values)
for _ in 0..<con2.count! {
// get a nested array
var con3 = try! con2.nestedUnkeyedContainer()
// decode all the elements of the nested array
var arr = [Any]()
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(String.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Int.self))
arr.append(try! con3.decode(Double.self))
arr.append(try! con3.decode(String.self))
bigarr.append(arr)
}
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
arrColumn.append(try! conColumns.decode(String.self))
// all done! finish initialization
self.tx_result = conResult
self.columns = arrColumn
self.values = bigarr
}
}
回答4:
This answer is build on top of the answer by @Orkhan Alikhanov
Since the values are Int
or String
, we can better represent them with an enum in place of Any
.
The following code can be pasted into Playground
JSON
So let's start with the JSON
let data = """
{
"values": [
[1, 1, 7, "Azuan Child", "Anak Azuan", "12345", "ACTIVE", "Morning", 7, 12, "2017-11-09 19:45:00"],
[28, 1, 0, "Azuan Child2", "Amran", "123456", "ACTIVE", "Evening", 1, 29, "2017-11-09 19:45:00"]
]
}
""".data(using: .utf8)!
Data Model
Now we can define our model (which will be Decodable
)
enum IntOrString: Decodable {
case int(Int)
case string(String)
init(from decoder: Decoder) throws {
if let string = try? decoder.singleValueContainer().decode(String.self) {
self = .string(string)
return
}
if let int = try? decoder.singleValueContainer().decode(Int.self) {
self = .int(int)
return
}
throw IntOrStringError.intOrStringNotFound
}
enum IntOrStringError: Error {
case intOrStringNotFound
}
}
As you can see we are explicitly saying that each value will be an Int
or a String
.
Response
And of course we need our Response
type.
struct Response: Decodable {
var values: [[IntOrString]]
}
Decoding
Now we can safely decode the JSON
if let response = try? JSONDecoder().decode(Response.self, from: data) {
let values = response.values
for value in values {
for intOrString in value {
switch intOrString {
case .int(let int): print("It's an int: \(int)")
case .string(let string): print("It's a string: \(string)")
}
}
}
}
Output
It's an int: 1
It's an int: 1
It's an int: 7
It's a string: Azuan Child
It's a string: Anak Azuan
It's a string: 12345
It's a string: ACTIVE
It's a string: Morning
It's an int: 7
It's an int: 12
It's a string: 2017-11-09 19:45:00
It's an int: 28
It's an int: 1
It's an int: 0
It's a string: Azuan Child2
It's a string: Amran
It's a string: 123456
It's a string: ACTIVE
It's a string: Evening
It's an int: 1
It's an int: 29
It's a string: 2017-11-09 19:45:00
回答5:
Solution
public struct UncertainValue<T: Decodable, U: Decodable>: Decodable {
public var tValue: T?
public var uValue: U?
public var value: Any? {
return tValue ?? uValue
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
tValue = try? container.decode(T.self)
uValue = try? container.decode(U.self)
if tValue == nil && uValue == nil {
//Type mismatch
throw DecodingError.typeMismatch(type(of: self), DecodingError.Context(codingPath: [], debugDescription: "The value is not of type \(T.self) and not even \(U.self)"))
}
}
}
Example
{
"results": [{
"name": "Gala",
"age": 1,
"type": "Pug"
}, {
"name": "Keira",
"age": "7",
"type": "Collie Rough"
}]
}
Usage
struct Dog: Decodable, CustomStringConvertible {
var name: String
var age: UncertainValue<Int, String>
var type: String
var description: String {
return "\(name) is a lovely \(type) of \(age.value!) years old"
}
}