Apollo iOS Swift does not convert JSONArray to String if you do not have object mapping in your schema.
I have a query where result array of Objects are not mapped in the schema.json
Description in the schema:
{"name":"stack",
"description":"",
"args":[
],
"type":{
"kind":"LIST",
"name":null,
"ofType":{
"kind":"SCALAR",
"name":"JSON",
"ofType":null
}}}
The received data looks like this:
"stack":[{
"name":"React",
"version":"",
"category":[ "JavaScript Frameworks"]}]
The error message I received is
[Apollo.GraphQLResultError(path: ["userHost", "stack"], underlying: Apollo.JSONDecodingError.couldNotConvert(value: {
category = (
React
);
name = "JavaScript Frameworks";
version = "";
}, to: Swift.String))]
I could only solve this by altering JSONStandardTypeConversions file.
It was:
extension String: JSONDecodable, JSONEncodable {
public init(jsonValue value: JSONValue) throws {
guard let string = value as? String else {
throw JSONDecodingError.couldNotConvert(value: value, to: String.self)
}
self = string
}
public var jsonValue: JSONValue {
return self
}
}
I changed it to
extension String: JSONDecodable, JSONEncodable {
public init(jsonValue value: JSONValue) throws {
let string = value as? String
if (string == nil) {
do {
let data1 = try JSONSerialization.data(withJSONObject: value, options: JSONSerialization.WritingOptions.prettyPrinted) // first of all convert json to the data
let convertedString = String(data: data1, encoding: String.Encoding.utf8) // the data will be converted to the string
if (convertedString == nil) {
throw JSONDecodingError.couldNotConvert(value: value, to: String.self)
} else {
self = convertedString ?? ""
}
} catch let myJSONError {
print(myJSONError)
throw JSONDecodingError.couldNotConvert(value: value, to: String.self)
}
} else {
self = string ?? ""
}
}
public var jsonValue: JSONValue {
return self
}
}
If standard conversion to the String does not work I am forcing JSON object to be converted to String. In this way, I am getting at least some data.
Related
I have struct like this:
struct Request {
var page: Int
var name: String?
var favoriteName: String?
var favoriteId: Int?
}
Then I convert it to Dictionary
func toDict() -> [String:Any] {
var dict = [String:Any]()
let otherSelf = Mirror(reflecting: self)
for child in otherSelf.children {
if let key = child.label {
dict[key] = child.value
}
}
return dict
}
If I loop it to modify and concatenate using those Dict key & value to create query string:
var queryString: String {
var output: String = ""
for (key,value) in toDict() {
output += "\(key)" + "=\(value)&"
}
output = String(output.dropLast())
return output
}
Does anyone know how to prevent nil, Optional(""), Optional(25) string added in the concatenation process ?
current result: page=20&name=nil&favoriteName=Optional("")&favoriteId=Optional(25)
expected result: page=20&name=&favoriteName=&favoriteId=25
Edit: Thank you everyone , wasn't really expecting so much answer tho. Let me edit the title to help future developers search.
The best practice is to use the URLComponents and URLQueryItem structs.
This is my approach to solving your problem.
First I added an enum to avoid having hardcoded strings.
struct Request {
var page: Int
var name: String?
var favoriteName: String?
var favoriteId: Int?
}
enum RequestValues: String {
case page
case name
case favoriteName
case favoriteId
}
Then I made this helper function to return the non nil vars from the Request instance as an array of URLQueryItem.
func createQuery(request: Request) -> [URLQueryItem] {
var queryItems: [URLQueryItem] = []
queryItems.append(URLQueryItem(name: RequestValues.page.rawValue, value: "\(request.page)"))
if let name = request.name {
queryItems.append(URLQueryItem(name: RequestValues.name.rawValue, value: name))
}
if let favoriteName = request.favoriteName {
queryItems.append(URLQueryItem(name: RequestValues.favoriteName.rawValue, value: favoriteName))
}
if let favoriteId = request.favoriteId {
queryItems.append(URLQueryItem(name: RequestValues.favoriteId.rawValue, value: "\(favoriteId)"))
}
return queryItems
}
Then you can get the query string like this:
let queryString = queryItems.compactMap({ element -> String in
guard let value = element.value else {
return ""
}
let queryElement = "\(element.name)=\(value)"
return queryElement
})
this will give you the expected result in your question.
page=20&name=&favoriteName=&favoriteId=25
But you should use the URLComponents struct to build your url as such.
func buildURL() -> URL? {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "google.com"
urlComponents.queryItems = queryItems
urlComponents.path = "/api/example"
urlComponents.url
guard let url = urlComponents.url else {
print("Could not build url")
return nil
}
return url
}
This would will give you the url with the query.
It would look like this :
https://google.com/api/example?page=5&name=nil&favoriteName=Hello&favoriteId=9
In model have optioanl var.
First, make your dictionary value optional
func toDict() -> [String: Any?] {
and then use the default value
var queryString: String {
var output: String = ""
for (key,value) in toDict() {
output += "\(key)" + "=\(value ?? "")&" // <== Here
}
output = String(output.dropLast())
return output
}
You can also prevent nil value in string
var queryString: String {
var output: String = ""
for (key,value) in toDict() where value != nil { //< Here
output += "\(key)" + "=\(value ?? "")&" //< Here
}
output = String(output.dropLast())
return output
}
Check if value is nil before insert it in the output, if the value is nil you can provide a default value:
var queryString: String {
var output: String = ""
for (key,value) in toDict() {
output += "\(key)"
if let value = value{
output += "=\(value)&"
}else{
output += "=DefaultValue&" // Replace DefaultValue with what is good for you
}
}
output = String(output.dropLast())
return output
}
This is more concise:
var queryString: String {
var output: String = ""
for (key,value) in toDict() {
output += "\(key)" + "=\(value ?? "DefaultValue")&"
}
output = String(output.dropLast())
return output
}
Instead of manually creating your string you should use URLComponents to generate your URL and use URLQueryItems for your parameters. I would make your structure conform to Encodable and encode NSNull in case the value is nil. Then you just need to pass an empty string to your query item when its value is NSNull:
This will force encoding nil values:
extension Request: Codable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(page, forKey: .page)
try container.encode(name, forKey: .name)
try container.encode(favoriteName, forKey: .favoriteName)
try container.encode(favoriteId, forKey: .favoriteId)
}
}
let request = Request(page: 20, name: nil, favoriteName: nil, favoriteId: 25)
do {
let dictiony = (try JSONSerialization.jsonObject(with: JSONEncoder().encode(request))) as? [String: Any] ?? [:]
let queryItems: [URLQueryItem] = dictiony.reduce(into: []) {
$0.append(.init(name: $1.key, value: $1.value is NSNull ? "" : String(describing: $1.value)))
}
print(queryItems)
var components = URLComponents()
components.scheme = "https"
components.host = "www.google.com"
components.path = "/search"
components.queryItems = queryItems
if let url = components.url {
print(url)
}
} catch {
print(error)
}
This will print
[name=, favoriteName=, page=20, favoriteId=25]
https://www.google.com/search?favoriteName=&name=&page=20&favoriteId=25
I am fetching some data from my Objective C classes. Here is my 'dataArray' data:
Optional(<__NSArrayM 0x2818cff60>(
{
date = 1574164423;
shakeState = 1;
},
{
date = 1574164431;
shakeState = 1;
}
)
)
I have created a Modal class 'ShakeInfo', that contain date and shakeState value.
I just want to convert this Array into array of 'ShakeInfo' object. My problem is when I am trying to print 'dataArray[0]' then I am getting error :
error: <EXPR>:3:1: error: value of type 'Optional<NSMutableArray>' has no subscripts
dataArray[0]
How can I read this array value index wise. Please advise me.
Edited:
Here is my code after getting dataArray:
do {
let data = try JSONSerialization.data(withJSONObject: dataArray!)
let responseStr = String(data: data, encoding: .utf8)!
print(responseStr)//[{"date":"1574164424","shakeState":1},{"date":"1574164430","shakeState":1}]
var shakeInfoDetails = [ShakeInfo]()
//how to add 'responseStr' value in the shakeInfoDetails Modal Array
} catch {
print("JSON serialization failed: ", error)
}
let data = #"[{"date":"1574164424","shakeState":1},{"date":"1574164430","shakeState":1}]"#.data(using: .utf8)!
//Model
struct ModelRecord : Codable {
var date : String
var shakeState : Int
}
class Model {
var date : Date
var shakeState : Int
init?(record: ModelRecord) {
guard let secondsFrom1970 = Double(record.date) else {
return nil
}
date = Date(timeIntervalSince1970: secondsFrom1970)
shakeState = record.shakeState
}
}
//Decoding
let decoder = JSONDecoder()
do {
let records = try decoder.decode([ModelRecord].self, from: data)
let models = records.compactMap { Model(record: $0) }
}
catch {
print("Decoding Error: \(error)")
}
Answer to original question:
let a1 : NSMutableArray? = NSMutableArray(array: ["A", "B", "C"])
a1?.object(at: 2)
Please read about the following:
Optionals in Swift
Codable - https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
Arrays in Swift Standard Library
NSMutableArray defined in Foundation
I have an answer with Firebase in this form:
["allNews": <__NSArrayM 0x6000015f06c0>(
{
createDate = "21.02.19";
creator = "lol#gmail.com";
creatorImageURL = "<null>";
creatorUID = kzorlyIOI3RgEjCV1XDLQUhu5CS2;
newsImageURL = "";
text = "Daft g s dfg ";
title = "Test ";
},
{
createDate = "21.02.19";
creator = "plol2#gmail.com";
creatorImageURL = "<null>";
creatorUID = Tw1JzFzcVbelRUA7GoFZ9CIWIwr1;
newsImageURL = "";
text = Vcbccvbvb;
title = hdbdvbccfb;
}
)
]
How can I parse it via the Codable protocol?
Below is my code:
struct AllNews: Codable {
var allNews: [DetailNews]
}
struct DetailNews: Codable {
var creator: String
var creatorUID: String
var title: String
var text: String
var createDate: String
var creatorImageURL: String
var newsImageURL: String
}
that's how I parse the data
guard let newsData = try? JSONSerialization.data(withJSONObject: document.data(), options: []) else { return }
let decodeJSON = JSONDecoder()
let allNews = try? decodeJSON.decode([DetailNews].self, from: newsData)
print(allNews)
but all the same allNews comes nil although the news data comes to me in the form of json which is attached above
You are receiving response in format:
[ "allNews": (
{
key : value
},
{
key : value
}
)]
As your news array lies in allNews key so you should pass AllNews struct to decode your response as:
guard let newsData = try? JSONSerialization.data(withJSONObject: document.data(), options: []) else { return }
let allNews = try? JSONDecoder().decode(AllNews.self, from: newsData)
print(allNews)
This should help you parse your data
let value = response.data
do {
let allValues = try JSONDecoder().decode([DetailNews].self, from: value)
} catch let error {
print(error)
}
Make sure value is of type Data
I have classes like these:
class MyDate
{
var year : String = ""
var month : String = ""
var day : String = ""
init(year : String , month : String , day : String) {
self.year = year
self.month = month
self.day = day
}
}
class Lad
{
var firstName : String = ""
var lastName : String = ""
var dateOfBirth : MyDate?
init(firstname : String , lastname : String , dateofbirth : MyDate) {
self.firstName = firstname
self.lastName = lastname
self.dateOfBirth = dateofbirth
}
}
class MainCon {
func sendData() {
let myDate = MyDate(year: "1901", month: "4", day: "30")
let obj = Lad(firstname: "Markoff", lastname: "Chaney", dateofbirth: myDate)
let api = ApiService()
api.postDataToTheServer(led: obj)
}
}
class ApiService {
func postDataToTheServer(led : Lad) {
// here i need to json
}
}
And I would like to turn a Lad object into a JSON string like this:
{
"firstName":"Markoff",
"lastName":"Chaney",
"dateOfBirth":
{
"year":"1901",
"month":"4",
"day":"30"
}
}
EDIT - 10/31/2017: This answer mostly applies to Swift 3 and possibly earlier versions. As of late 2017, we now have Swift 4 and you should be using the Encodable and Decodable protocols to convert data between representations including JSON and file encodings. (You can add the Codable protocol to use both encoding and decoding)
The usual solution for working with JSON in Swift is to use dictionaries. So you could do:
extension Date {
var dataDictionary {
return [
"year": self.year,
"month": self.month,
"day": self.day
];
}
}
extension Lad {
var dataDictionary {
return [
"firstName": self.firstName,
"lastName": self.lastName,
"dateOfBirth": self.dateOfBirth.dataDictionary
];
}
}
and then serialize the dictionary-formatted data using JSONSerialization.
//someLad is a Lad object
do {
// encoding dictionary data to JSON
let jsonData = try JSONSerialization.data(withJSONObject: someLad.dataDictionary,
options: .prettyPrinted)
// decoding JSON to Swift object
let decoded = try JSONSerialization.jsonObject(with: jsonData, options: [])
// after decoding, "decoded" is of type `Any?`, so it can't be used
// we must check for nil and cast it to the right type
if let dataFromJSON = decoded as? [String: Any] {
// use dataFromJSON
}
} catch {
// handle conversion errors
}
If you just need to do this for few classes, providing methods to turn them into dictionaries is the most readable option and won't make your app noticeably larger.
However, if you need to turn a lot of different classes into JSON it would be tedious to write out how to turn each class into a dictionary. So it would be useful to use some sort of reflection API in order to be able to list out the properties of an object. The most stable option seems to be EVReflection. Using EVReflection, for each class we want to turn into json we can do:
extension SomeClass: EVReflectable { }
let someObject: SomeClass = SomeClass();
let someObjectDictionary = someObject.toDictionary();
and then, just like before, we can serialize the dictionary we just obtained to JSON using JSONSerialization. We'll just need to use object.toDictionary() instead of object.dataDictionary.
If you don't want to use EVReflection, you can implement reflection (the ability to see which fields an object has and iterate over them) yourself by using the Mirror class. There's an explanation of how to use Mirror for this purpose here.
So, having defined either a .dataDictionary computed variable or using EVReflection's .toDictionary() method, we can do
class ApiService {
func postDataToTheServer(lad: Lad) {
//if using a custom method
let dict = lad.dataDictionary
//if using EVReflection
let dict = lad.toDictionary()
//now, we turn it into JSON
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict,
options: .prettyPrinted)
// send jsonData to server
} catch {
// handle errors
}
}
}
May this GitHub code will help you.
protocol SwiftJsonMappable {
func getDictionary() -> [String: Any]
func JSONString() -> String
}
extension SwiftJsonMappable {
//Convert the Swift dictionary to JSON String
func JSONString() -> String {
do {
let jsonData = try JSONSerialization.data(withJSONObject: self.getDictionary(), options: .prettyPrinted)
// here "jsonData" is the dictionary encoded in JSON data
let jsonString = String(data: jsonData, encoding: .utf8) ?? ""
// here "decoded" is of type `Any`, decoded from JSON data
return jsonString
// you can now cast it with the right type
} catch {
print(error.localizedDescription)
}
return ""
}
//Convert Swift object to Swift Dictionary
func getDictionary() -> [String: Any] {
var request : [String : Any] = [:]
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if let lable = child.label {
//For Nil value found for any swift propery, that property should be skipped. if you wanna print nil on json, disable the below condition
if !checkAnyContainsNil(object: child.value) {
//Check whether is custom swift class
if isCustomType(object: child.value) {
//Checking whether its an array of custom objects
if isArrayType(object: child.value) {
if let objects = child.value as? [AMSwiftBase] {
var decodeObjects : [[String:Any]] = []
for object in objects {
//If its a custom object, nested conversion of swift object to Swift Dictionary
decodeObjects.append(object.getDictionary())
}
request[lable] = decodeObjects
}
}else {
//Not an arry, and custom swift object, convert it to swift Dictionary
request[lable] = (child.value as! AMSwiftBase).getDictionary()
}
}else {
request[lable] = child.value
}
}
}
}
return request
}
//Checking the swift object is swift base type or custom Swift object
private func isCustomType(object : Any) -> Bool {
let typeString = String(describing: type(of: object))
if typeString.contains("String") || typeString.contains("Double") || typeString.contains("Bool") {
return false
}
return true
}
//checking array
private func isArrayType(object : Any) -> Bool {
let typeString = String(describing: type(of: object))
if typeString.contains("Array"){
return true
}
return false
}
//Checking nil object
private func checkAnyContainsNil(object : Any) -> Bool {
let value = "\(object)"
if value == "nil" {
return true
}
return false
}
}
https://github.com/anumothuR/SwifttoJson
Currently I'm trying to return values from JSON data but am running into an issue where one of the values is returning null thereby causing my app to crash.
This is what the json looks like:
"results": [
{
"genre_ids": [
99
],
"id": 440108,
"poster_path": null, //The value that's causing incredible headaches!
},
{
"genre_ids": [
99,
10402
],
"id": 391698,
"poster_path": "/uv7syi4vRyjvWoB8qExbqnbuCu5.jpg",//The value trying to get!
},
]
I'm using the json object mapper Gloss which has been good to this point. Here is how I have my objects set up:
public struct ResultsGenrePosters: Decodable {
public let results : [GenrePosters]?
public init?(json: JSON) {
results = "results" <~~ json
}
}
public struct GenrePosters: Decodable, Equatable{
public let poster : String
public init? (json: JSON) {
guard let poster: String = "poster_path" <~~ json
else {return nil}
self.poster = poster
}
public static func ==(lhs: GenrePosters, rhs: GenrePosters) -> Bool {
return lhs.poster == rhs.poster
}
static func updateGenrePoster(genreID: NSNumber, urlExtension: String, completionHandler:#escaping (_ details: [String]) -> Void){
let nm = NetworkManager.sharedManager
nm.getJSONData(type:"genre/\(genreID)", urlExtension: urlExtension, completion: {
data in
if let jsonDictionary = nm.parseJSONData(data)
{
guard let genrePosters = ResultsGenrePosters(json: jsonDictionary)
else {
print("Error initializing object")
return
}
guard let posters = genrePosters.results
else {
print("No poster exists for genre: \(genreID)")// This keeps on triggering when the null object is hit, this is where it would be good to move to the next array to get that value
return
}
let postersArray = posters.map {$0.poster}// converts custom object "GenrePosters" to String(poster value)
completionHandler(postersArray)
}
})
}
}
guard let poster: String = "poster_path" <~~ json
should be
guard let poster = ("poster_path" <~~ json) as? String
(Of course you are using a library that I know nothing about, so the library could be crashing. JSONSerialization is your friend. It works. Everyone knows what it does).
for data in results as! [Dictionary<String,AnyObjects>]{
var value1 = data["key"] as? String
if value1 == nil{value1 = ""}else{}
}
}
That was just a simple logic but completely work for me all the time. :)