I have the folllowing Json response:
` [Body]:
{"id":"cmpl-6Z45N6vfd2312vdfqyYPb8aDRVe9Nft7vfnpoEsL","object":"text_completion","created":16738vfv15205,"model":"text-davinci-003","choices":[{"text":"\n1. Full Stack Developer\n2. Project Engineering Intern\n3. AI Programmer\n4. Systems Trade Studies Engineer\n5. BLE Technology Developer\n6. macOS Menu Bar App Developer\n7. Mobile App Developer\n8. Research Engineer\n9. Writing and Design Lab Researcher","index":0,"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":726,"completion_tokens":60,"total_tokens":786}}`
And I'm using SwiftyJson and Alamofire to parse the Json using the following code:
` AF.request(...).responseJSON { response in
print("response: \(response)")
switch response.result {
case .success(_):
if let JSON = response.value as! [[String : Any]]?{
print("JSON: \(JSON)")
let dic = JSON[0] as [String:AnyObject]?
print("TitularEmail : ",dic?["choices"])
}
break
case .failure(_):
print("There is an error")
break
}
}`
But it keeps breaking and I keep getting the error 'Thread 1: signal SIGABRT' at 'if let JSON = response.value as! [[String : Any]]?{'
Does anyone know what could've gone wrong? I've read through quite some threads but I can't seem to figure this out. Any info shall be appreciated.
Using the built-in Decodable would be a much better alternative. At the risk of slightly wandering from the original SwiftyJSON question, I highly recommend using Decodable to parse JSON (see also Encodable for writing and Codable for the combination of both). Here's an example of a very basic use of Decodable. Supposed you want to parse the following:
{
"name": "New York City",
"mayor": "Eric Adams",
"population": 8468000
}
You can parse it super easily with Decodable:
struct City: Decodable {
let name:String
let mayor:String
let population:Int
}
You can create an instance of City in this example by taking the JSON– assume it is stored in a variable called jsonData– and simply doing the following:
let NYC = try JSONDecoder().decode(City.self, from: jsonData)
You should highly consider this route because it can get a lot done with very little work.
You can also use JSONSerialization if you don't want to use the aforementioned ways of parsing it. Here is a simple tutorial on how to do that.
In your case, you can implement Decodable to solve your problem like this:
var json = try! Data(contentsOf: URL(fileURLWithPath: "PATH TO FILE WITH EXAMPLE JSON TEXT"))
struct FirstStruct:Decodable {
let id:String
let object:String
let created:String //I'm calling this a string because, despite the lack of quotes, it has letters in it and cannot be an Int. You could also do something like Int? so it can fail safely if there are letters. I am adding quotes to the example text make it work.
let model:String
let choices:[SecondStruct]
let usage:ThirdStruct
}
struct SecondStruct:Decodable {
let text:String
let index:Int
let logprobs:String? //idk what type this is supposed to be from the text given
let finish_reason:String
}
struct ThirdStruct:Decodable {
let prompt_tokens:Int
let completion_tokens:Int
let total_tokens:Int
}
var test = try! JSONDecoder().decode(FirstStruct.self, from: json)
print(test.id)
print(test.usage.total_tokens) //these two examples show it clearly works here
Obviously, have a bit more care with error handling, try! was just easier for the example.
Related
Here's my problem. Let's say I have a JSON structure that I'm reading using Swift's Codable API. What I want to do is not decode part of the JSON but read it as a string even though it's valid JSON.
In a playground I'm messing about with this code:
import Foundation
let json = #"""
{
"abc": 123,
"def": {
"xyz": "hello world!"
}
}
"""#
struct X: Decodable {
let abc: Int
let def: String
enum CodingKeys: String, CodingKey {
case abc
case def
}
init(decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
abc = try container.decode(Int.self, forKey: .abc)
var defContainer = try container.nestedUnkeyedContainer(forKey: .def)
def = try defContainer.decode(String.self)
// def = try container.decode(String.self, forKey: .def)
}
}
let x = try JSONDecoder().decode(X.self, from: json.data(using: .utf8)!)
Essentially I'm trying to read the def structure as a string instead of a dictionary.
Any clues?
If the resulting string doesn't need to be identical to the corresponding text in the JSON file (i.e. preserve whatever white space is there, etc.), just decode the entire JSON, and then encode the part that you want as a string, and construct a string from the resulting data.
If you do want to preserve exactly the text in the original JSON, including white space, then you'll do better to get that string some other way. Foundation's Scanner class makes it pretty easy to look for some starting token (e.g. "def:") and then read as much data as you want. So consider decoding the JSON in one step, and then separately using a Scanner to dig through the same input data to get the string you need.
Definitely not using JSONDecoder. By the time init(from:) is called, the underlying data has already been thrown away. However you do it, you'll need to parse the JSON yourself. This isn't as hard as it sounds. For example, to extract this string, you could use JSONScanner, which is a few hundred lines of code that you can adjust as you like. With that, you can do things like:
let scanner = JSONScanner()
let string = try scanner.extractData(from: Data(json.utf8), forPath: ["def"])
print(String(data: string, encoding: .utf8)!)
And that will print out:
{
"xyz": "hello world!"
}
(Note that RNAJSON is a sandbox framework of mine. It's not a production-ready framework, but it does a lot of interesting JSON things.)
Integrating this into a system that decodes this in a "Decoder-like" way is definitely buildable along these lines, but there's no simple answer down that path. Caleb's suggestion of re-encoding the data into a JSON string is definitely the easiest way.
Using RNAJSON again, there's a type called JSONValue that you can use like this:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
abc = try container.decode(Int.self, forKey: .abc)
let defJSON = try container.decode(JSONValue.self, forKey: .def)
def = String(data: try JSONEncoder().encode(defJSON), encoding: .utf8)!
}
This will make def be a JSON string, but it doesn't promise that key order is maintained or that whitespace is preserved.
Thanks everyone. It's an interesting problem. Not so much in the rather simple example I gave, but in the actual problem I'm thinking about I want to include mustache templating inside the text I'm decoding (which by the way could be JSON or YAML). The top level parts of the data are fixed in that they have pre-defined keys so reading them is fine. but there is a point in the data where the developer generating it can pretty much include any data structure they like, and that data structure can include mustache keys. ie. abc: {{some-value}} which of course, makes no sense to a decoder.
There's lots of ways this could go wrong but I'm thinking that really I would need to run mustache across the entire data structure before decoding it, where as currently I'm decoding then running mustache and to do that I'm already setting the unknown part as a string and reading it as such. I'm contemplating the idea that it would be much nicer if it was just JSON/YAML rather than a string containing JSON/YAML. But it might not be possible :-)
I have started on someone's project having difficulties in parsing the response into Codable model. Below is the code snippet, which returns me resultData as AnyObject
switch response.result {
case .success(let resultData):
taskCallback(1, resultData as AnyObject)
case .failure(let error):
print(error)
}
I have been trying to parse the response into Codable model but it requires me AnyObject of type Data. How do I convert AnyObject into type Data.
let model = try JSONDecoder().decode([SomeModel].self, from: resultData)
I can't alter the completion handler response as its being used at so many places
[Here is the screenshot of printed response of responseObject]
[1]: https://i.stack.imgur.com/g6RRM.png
JSON is almost certainly a wrapper around JSONSerialization (there is no stdlib or Foundation type called "JSON"). So if you want to turn that into Data, you would use JSONSerialization again. For example:
let data = try JSONSerialization.data(withJSONObject: resultData)
This is of course very inefficient (you're decoding the JSON, re-encoding it, and the decoding it a second time), but it may be a stop-gap measure as you refactor to get rid of the AnyObject and use Decodable for the initial decoding.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
The new Swift "Decoder" class sounds like a great way to parse JSON data, but all of the examples I've found use a well-known, well-defined 'struct' to do so.
In my case I'm querying an arbitrary website that returns a HUGE JSON string and I only care about a few of the (deeply nested) fields, so I don't want to take all that time to define a 'struct' to get at them.
Is it even possible to do this with "Decoder"? And if so, how does one go about it?
The question seems to be based on a misapprehension about how Decodable works. As a convenience, Decodable is willing to do some automatic code generation behind the scenes so that you can define a struct or nest of structs and just decode the entirety of the JSON. But you are not required to take advantage of that in order to decode JSON.
There is no need to define struct properties for "fields" you don't care about. If a JSON dictionary contains 100 keys and your corresponding struct contains just one property, no problem; that key will be fetched, and no others.
With regard to the "deeply nested" part, it should not take you much time to write simple nested structs that perform the dive to reach the dictionary you really care about. But if you don't want to do even that, you could write an implementation of init(from:) that dives down and fetches out the desired values.
In other words, if you think of Decodable as consisting primarily of your implementation of init(from:), and learn to write the code that it needs, you will see that this JSON can be parsed in a few quick simple lines of code.
As an example, here's a JSON sketch of a deeply nested piece of information with a bunch of extra information at every level that we want to ignore:
{
"ignore": true,
"outer1": {
"ignore": true,
"outer2": {
"ignore": true,
"outer3": {
"name": "matt",
"ignore": true
}
}
}
}
What I'd like to do is define a very simple struct Person that consists solely of the deeply nested name:
struct Person : Decodable {
let name : String
}
I can do that! To do so, I implement Decodable myself, supplying a "hoover" CodingKey adopter struct and an implementation of init(from:), like this (this may look like a lot of work, but it isn't, because the AnyCodingKey implementation is boilerplate, copied and pasted from here, and the init(coder:) implementation is just a few lines of code that were easy to write):
struct Person : Decodable {
let name : String
struct AnyCodingKey : CodingKey {
var stringValue: String
var intValue: Int?
init(_ codingKey: CodingKey) {
self.stringValue = codingKey.stringValue
self.intValue = codingKey.intValue
}
init(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
var con = try! decoder.container(keyedBy: AnyCodingKey.self)
con = try! con.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue:"outer1"))
con = try! con.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue:"outer2"))
con = try! con.nestedContainer(keyedBy: AnyCodingKey.self, forKey: AnyCodingKey(stringValue:"outer3"))
let name = try! con.decode(String.self, forKey: AnyCodingKey(stringValue:"name"))
self.name = name
}
}
When I want to dive into the JSON and grab the name information, it's trivial:
let person = try! JSONDecoder().decode(Person.self, from: json)
The result is a Person object with name value "matt". Note that I didn't have to add any of the ignore keys and I didn't need to make a nest of structs.
Sure you can achieve this but with both JSonSerialization & Decodable , you have to serialize the json until reach the desired content then decode it ,but instead I recommend to create structs for root keys only then decode , think of it as it's a path from top to bottom don't decode a key that isn't in the path of your desired content
In an iPhone app using SWIFT, I'm having to deal with a third-party API that sends a escaped string instead of a JSON object as response.
The response looks like this:
"[
{
\"ID\":3880,
\"Name\":\"Exploration And Production Inc.\",
\"ContractNumber\":\"123-123\",
\"Location\":\"Booker #1\",
\"Volume\":1225.75,
\"OtherFees\":10.0
}
]"
Up until now I have been dealing with this by manipulating the string to remove the unwanted characters until I get a JSON-like string and then parsing that as usual.
Angular has a handy function to deal with this:
angular.fromJson(response.data);
Java has its own way to deal with it. Is an equivalent function in Swift?
If you are parsing it to a dictionary then the simplest solution will be to convert String to Data and use JSONSerialization:
if let data = string.data(using: .utf8) {
do {
let responseArray = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]]
print(responseArray)
}
catch {
print(error)
}
}
But ofcourse it would be better to process it as a Codable model in which case its again just as simple as:
try JSONDecoder().decode([Item].self, from: data)
Provided that you have a valid Decodable model like so:
struct Item: Decodable {
let id: Int
let name: String
//add the other keys you want to use (note its type sensitive)
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
}
}
Lastly, avoid stringified jsons because its an easy source of errors.
Malformed strings or small/large deviations in the structure can go easily unnoticed.
Let the backend team know that they should follow a protocol in their API that its consumers can rely on.
By setting the json format, its essentially like a contract that showcases its content and purpose with clarity.
Sending a stringified json is simply lazy and reflects poorly on its designer, imho.
I'm using HTTPTask to load data from openweathermap.org. Which is working fine. I'm having trouble converting the data to JSON. I'd like to use SwiftyJSON but, I can't quite figure out how to bridge the two.
HTTPTask has a JSON Serializer, which I got working, but I rather use Swifty, it's seems easier to work with.
Here's what I have so far. This loads the weather from openweathermap.org. I'm not sure how to pass the response into Swifty.
var request = HTTPTask()
request.requestSerializer = JSONRequestSerializer()
request.responseSerializer = JSONResponseSerializer()
request.GET(openWeatherURL, parameters: ["q":"San Francisco", "APPID":openWeatherAPIKey], success: {(response: HTTPResponse) in
if let dict = response.responseObject as? Dictionary<String, AnyObject> {
println("Response: \(response)")
println("Dictionary: \(dict)")
let description = dict["weather"]["description"]
println(description)
}
}, failure: {(error: NSError, repsonse: HTTPResponse?) in
println("error \(error)")
})
SwiftyJSON is quite happy to take a variety of objects, including Dictionary?, so just go for it!
let dict = JSON(response.responseObject)