How to convert json value into array of ints [Swift] - swift

In my swift app I get this JSON: ["jsonArray": "[15,16]"] by using this code:
guard let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:String] else { return }
My question is: How can I convert json["jsonArray"], that is "[15,16]", into an array of ints?

Your JSON is invalid. I assume you meant {"jsonArray": "[15,16]"} (curly brackets at the outer level).
On top of that, it is horribly encoded. If you get a chance, get the developer on the other end to change it. In case you cannot, you can first decode it as a string, then decode it a second time to get the integers:
struct Response: Decodable {
private struct RawResponse: Decodable {
let jsonArray: String
}
var numbers: [Int]
init(from decoder: Decoder) throws {
// First decode the array as a string
let rawResponse = try RawResponse(from: decoder)
// Then turn it into a Data struct
let jsonData = rawResponse.jsonArray.data(using: .utf8)!
// And finally decode it as an Int array
self.numbers = try JSONDecoder().decode([Int].self, from: jsonData)
}
}
let response = try JSONDecoder().decode(Response.self, from: json)
print(response)

Related

Swift Invalid type in JSON write error using JSONSerialization.data

I have an array with elements of a custom type. Here is the type :
public class RequestElemDataBody: Codable {
public var name: String
public var value: String
public init(name: String, value: String) {
self.name = name
self.value = value
}
}
This is how I declare my array :
var elementsInForm = [RequestElemDataBody]()
I use a function to convert this array to Data then to String :
func json(from object: [Any]) -> String? {
guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
return nil
}
return String(data: data, encoding: String.Encoding.utf8)
}
When executing I get this error message :
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (Data.RequestElemDataBody)'
I don't know what is wrong with my custom type since it is Codable.
How can I parse my array with my function without it throwing an error ?
You should use JSONEncoder when serializing Codable.
The example from the documentation page:
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
let pear = GroceryProduct(name: "Pear", points: 250, description: "A ripe pear.")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(pear)
print(String(data: data, encoding: .utf8)!)
/* Prints:
{
"name" : "Pear",
"points" : 250,
"description" : "A ripe pear."
}
*/
Problem with your approach is that you try to encode the whole array. But your codable object is not an array at the moment.
Try passing single element to your method and return the string to work with it outside;
func json(from element: RequestElemDataBody) -> String {
...
}
As mentioned here the top level object can be Array or Dictionary and all objects can be instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
. In your case they are instances of an custom object.
So you can try converting the objects to dictionary first and then use JSONSerialization
And to convert to dictionary you can create an extension on Encodable
extension Encodable {
var dict : [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else { return nil }
return json
}
}
and while calling function you can use ArrayName.compactMap({$0.dict})
Also you can use ObjectMapper library.
Reference from: How to convert a Swift object to a dictionary

Swift Codable support for objects represented as array

I'm trying to encode and decode data from an API that represent an object as an array of strings, for instance:
[
["username", "message", "date"],
["username", "message", "date"],
["username", "message", "date"]
]
This is the corresponding Codable struct:
struct Message: Codable {
let user: String
let content: String
let date: String
private enum CodingKeys: Int, CodingKey {
case user = 0
case content = 1
case date = 2
}
}
Neither encoding or decoding work; encoding shows that a JSON object is created instead of an array:
let msg = Message(user: "foo", content: "content", date: "2019-06-04")
let jsonData = try! JSONEncoder().encode(msg)
let jsonString = String(data: jsonData, encoding: .utf8)!
The final string is:
{"content":"content","user":"foo","date":"2019-06-04"}
My goal is to obtain the following string
["foo", "content", "2019-06-04"]
Using a custom encode/decode method in the struct solves this, but forces to create a lot of boilerplate for each struct/class.
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let values = try container.decode([String].self)
user = values[CodingKeys.user.rawValue]
content = values[CodingKeys.content.rawValue]
date = values[CodingKeys.date.rawValue]
}
How would one proceed to support this for any object?
And yes, this is a weird API, but this is not the first time I encounter one of those and user a different API format is not what I'm looking for here.
Rather than singleValueContainer use unkeyedContainer, it's more robust. If you want to assign the array items to struct members you have to write a custom initializer anyway
struct Message: Codable {
let user: String
let content: String
let date: String
init(from decoder: Decoder) throws {
var arrayContainer = try decoder.unkeyedContainer()
guard arrayContainer.count == 3 else { throw DecodingError.dataCorruptedError(in: arrayContainer, debugDescription: "The array must contain three items") }
user = try arrayContainer.decode(String.self)
content = try arrayContainer.decode(String.self)
date = try arrayContainer.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var arrayContainer = encoder.unkeyedContainer()
try arrayContainer.encode(contentsOf: [user, content, date])
}
}

JSONEncoder won't allow type encoded to primitive value

I'm working on an implementation of Codable for an enum type with possible associated values. Since these are unique to each case, I thought I could get away with outputting them without keys during encoding, and then simply see what I can get back when decoding in order to restore the correct case.
Here's a very much trimmed down, contrived example demonstrating a sort of dynamically typed value:
enum MyValueError : Error { case invalidEncoding }
enum MyValue {
case bool(Bool)
case float(Float)
case integer(Int)
case string(String)
}
extension MyValue : Codable {
init(from theDecoder:Decoder) throws {
let theEncodedValue = try theDecoder.singleValueContainer()
if let theValue = try? theEncodedValue.decode(Bool.self) {
self = .bool(theValue)
} else if let theValue = try? theEncodedValue.decode(Float.self) {
self = .float(theValue)
} else if let theValue = try? theEncodedValue.decode(Int.self) {
self = .integer(theValue)
} else if let theValue = try? theEncodedValue.decode(String.self) {
self = .string(theValue)
} else { throw MyValueError.invalidEncoding }
}
func encode(to theEncoder:Encoder) throws {
var theEncodedValue = theEncoder.singleValueContainer()
switch self {
case .bool(let theValue):
try theEncodedValue.encode(theValue)
case .float(let theValue):
try theEncodedValue.encode(theValue)
case .integer(let theValue):
try theEncodedValue.encode(theValue)
case .string(let theValue):
try theEncodedValue.encode(theValue)
}
}
}
let theEncodedValue = try! JSONEncoder().encode(MyValue.integer(123456))
let theEncodedString = String(data: theEncodedValue, encoding: .utf8)
let theDecodedValue = try! JSONDecoder().decode(MyValue.self, from: theEncodedValue)
However, this is giving me an error during the encoding stage as follows:
"Top-level MyValue encoded as number JSON fragment."
The issue appears to be that, for whatever reason, the JSONEncoder won't allow a top-level type that isn't a recognised primitive to be encoded as a single primitive value. If I change the singleValueContainer() to an unkeyedContainer() then it works just fine, except that of course the resulting JSON is an array, not a single value, or I can use a keyed container but this produces an object with the added overhead of a key.
Is what I'm trying to do here impossible with a single value container? If not, is there some workaround that I can use instead?
My aim was to make my type Codable with a minimum of overhead, and not just as JSON (the solution should support any valid Encoder/Decoder).
There is a bug report for this:
https://bugs.swift.org/browse/SR-6163
SR-6163: JSONDecoder cannot decode RFC 7159 JSON
Basically, since RFC-7159, a value like 123 is valid JSON, but JSONDecoder won't support it. You may follow up on the bug report to see any future fixes on this. [The bug was fixed starting in iOS 13.]
#Where it fails#
It fails in the following line of code, where you can see that if the object is not an array nor dictionary, it will fail:
https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/JSONSerialization.swift#L120
open class JSONSerialization : NSObject {
//...
// top level object must be an Swift.Array or Swift.Dictionary
guard obj is [Any?] || obj is [String: Any?] else {
return false
}
//...
}
#Workaround#
You may use JSONSerialization, with the option: .allowFragments:
let jsonText = "123"
let data = Data(jsonText.utf8)
do {
let myString = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(myString)
}
catch {
print(error)
}
Encoding to key-value pairs
Finally, you could also have your JSON objects look like this:
{ "integer": 123456 }
or
{ "string": "potatoe" }
For this, you would need to do something like this:
import Foundation
enum MyValue {
case integer(Int)
case string(String)
}
extension MyValue: Codable {
enum CodingError: Error {
case decoding(String)
}
enum CodableKeys: String, CodingKey {
case integer
case string
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodableKeys.self)
if let integer = try? values.decode(Int.self, forKey: .integer) {
self = .integer(integer)
return
}
if let string = try? values.decode(String.self, forKey: .string) {
self = .string(string)
return
}
throw CodingError.decoding("Decoding Failed")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodableKeys.self)
switch self {
case let .integer(i):
try container.encode(i, forKey: .integer)
case let .string(s):
try container.encode(s, forKey: .string)
}
}
}
let theEncodedValue = try! JSONEncoder().encode(MyValue.integer(123456))
let theEncodedString = String(data: theEncodedValue, encoding: .utf8)
print(theEncodedString!) // { "integer": 123456 }
let theDecodedValue = try! JSONDecoder().decode(MyValue.self, from: theEncodedValue)

Parse complex json code

I have the following JSON code and want to parse it in Swift. I use Alamofire to get the JSON and have created a struct for the parsing:
{
"-8802586561990153106-1804221538-5":{
"zug":{
"klasse":"RB",
"nummer":"28721"
},
"ankunft":{
"zeitGeplant":"1804221603",
"zeitAktuell":"1804221603",
"routeGeplant":[
"Wiesbaden Hbf",
"Mainz Hbf"
]
},
"abfahrt":{
"zeitGeplant":"1804221604",
"zeitAktuell":"1804221604",
"routeGeplant":[
"Gro\u00df Gerau",
"Klein Gerau",
"Weiterstadt"
]
}
},
"8464567322535526441-1804221546-15":{
"zug":{
"klasse":"RB",
"nummer":"28724"
},
"ankunft":{
"zeitGeplant":"1804221657",
"zeitAktuell":"1804221708",
"routeGeplant":[
"Aschaffenburg Hbf",
"Mainaschaff"
]
},
"abfahrt":{
"zeitGeplant":"1804221658",
"zeitAktuell":"1804221709",
"routeGeplant":[
"Mainz-Bischofsheim"
]
}
}
}
I have created a struct for this that looks like this:
struct CallResponse: Codable {
struct DirectionTrain: Codable {
struct Train: Codable {
let trainClass: String
let trainNumber: String
}
struct Arrival: Codable {
let line: String
let eta: Date
let ata: Date
let platform: String
let route: [String]
}
struct Departure: Codable {
let line: String
let etd: Date
let atd: Date
let platform: String
let route: [String]
}
}
}
The rest of my code is:
Alamofire.request(url!).responseJSON { response in
switch response.result {
case .success:
let decoder = JSONDecoder()
let parsedResult = try! decoder.decode(CallResponse.self, from: response.data!)
case .failure(let error):
print(error)
}
}
When I run this code the error message is:
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "train", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"train\", intValue: nil) (\"train\").", underlyingError: nil))
Can anyone help me find my problem? Thank you for your answers!
The problem is merely that your structs look nothing at all like your JSON!
Your JSON is a dictionary whose keys have names like "-8802586561990153106-1804221538-5" and "8464567322535526441-1804221546-15". But I don't see you declaring any struct that deals with those keys.
Then each of those turns out to be a dictionary with keys like "zug", "ankunft", and "abfahrt". But I don't see you declaring any struct that deals with those keys either.
And then the "zug" has keys "klasse" and "nummer"; you don't have those either.
And so on.
Either your structs must look exactly like your JSON, or else you must define CodingKeys and possibly also implement init(from:) to deal with any differences between your structs and your JSON. I suspect that the keys "-8802586561990153106-1804221538-5" and "8464567322535526441-1804221546-15" are unpredictable, so you will probably have to write init(from:) in order to deal with them.
For example, I was able to decode your JSON like this (I do not really recommend using try!, but we decoded without error and it's just a test):
struct Entry : Codable {
let zug : Zug
let ankunft : AnkunftAbfahrt
let abfahrt : AnkunftAbfahrt
}
struct Zug : Codable {
let klasse : String
let nummer : String
}
struct AnkunftAbfahrt : Codable {
let zeitGeplant : String
let zeitAktuell : String
let routeGeplant : [String]
}
struct Top : Decodable {
var entries = [String:Entry]()
init(from decoder: Decoder) throws {
struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
let con = try! decoder.container(keyedBy: CK.self)
for key in con.allKeys {
self.entries[key.stringValue] =
try! con.decode(Entry.self, forKey: key)
}
}
}
// d is a Data containing your JSON
let result = try! JSONDecoder().decode(Top.self, from: d)

swift4 JSONDecoder. How to decode an array with different type inside?

JSON to decode:
{
"jsonrpc": "2.0",
"result": [
{
"code": 1,
"message": "error"
},
[
{
"gid": "123"
....
}
]
....
]
}
"JSONSerialization" is complex to decode this json.
let str = """
{"jsonrpc": "2.0","result": [{"code": 1,"message": "error"},[{"gid": "123"}]]}
"""
let data = str.data(using: .utf8)!
if let json = try! JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any],
let result = json["result"] as? [Any] {
let error = result.map { $0 as? [Any] }.filter { $0 != nil }
let objs = result.map { $0 as? [String: Any] }.filter { $0 != nil }
print(error)
print(objs)
}
Is there a way to decode the JSON payload with JSONDecoder to [Data] or anything else.
struct Result: Codable {
let result: [???] //can't use [Data] here
}
Is there a way to decode the JSON payload with JSONDecoder to [Data] or anything else.
The decode method on JSONDecoder requires some type that conforms to JSONDecodable. True, Any does not conform to Decodable and neither does Data or [Data]. You probably want to define a custom Swift type to represent your JSON and make that conform to Decodable.
How to decode an array with different type inside?
The reason we want to see the complete JSON is that it is useful to know whether the dictionaries and arrays inside result are consistent. If the entries are all like your example, then perhaps you could do something like this:
struct JSONRPC: Decodable {
enum Result: Decodable {
struct ArrayItem: Decodable {
let code: Int
let message: String
}
case array([ArrayItem])
case dictionary(Dictionary<String, String>)
init(from decoder: Decoder) throws {
// Implement decoder for enum with associated values.
// This will not "just work" without your additional
// instructions specifying how to do it. But that's an
// answer for a separate question, I think.
}
}
let version: String
let results: [Result]
private enum CodingKeys: String, CodingKey {
case version = "jsonrpc"
case results = "result"
}
}
With that in place, you can use it like this:
let decoder = JSONDecoder()
let payload = try decoder.decode(JSONRPC.self, from: data)
If the decoding succeeds, then payload will be an instance of RPCJSON.