I have a JSON object, which has the key value:
"content":""
Actually, the value of content is 7 BOM characters, you can copy it into SublimeText to view it.
But when I use the command in the Swift:
let object = try JSONDecoder().decode(type, from: data)
then object.content only has 6 characters.
Do you know why and how to fix it?
This full example shows the issue:
import Foundation
let bom: Character = "\u{FEFF}"
let string = String(repeating: bom, count: 7)
print(string.count) // 7
let json = #"{"content": "\#(string)" }"#.data(using: .utf8)!
struct Type: Decodable {
let content: String
}
let decoded = try! JSONDecoder().decode(Type.self, from: json)
print(decoded.content.count) // 6
One of the special characters is lost during JSON parsing.
Related
How do I parse JSON like this:
let json = "{\"key\":18446744073709551616}"
struct Foo: Decodable {
let key: UInt64
}
let coder = JSONDecoder()
let test = try! coder.decode(Foo.self, from: json.data(using: .utf8)!)
The problem is that that number is too big for UInt64. I know of no larger integer types in Swift.
Parsed JSON number <18446744073709551616> does not fit in UInt64
I wouldn't mind getting it as String or Data, but that's not allowed because JSONDecoder knows it's supposed to be a number:
Expected to decode String but found a number instead.
You can use Decimal instead:
let json = "{\"key\":184467440737095516160000001}"
struct Foo: Decodable {
let key: Decimal
}
let coder = JSONDecoder()
let test = try! coder.decode(Foo.self, from: json.data(using: .utf8)!)
print(test) // Foo(key: 184467440737095516160000001)
Decimal is the Swift overlay type of NSDecimalNumber which
... can represent any number that can be expressed as mantissa x 10^exponent where mantissa is a decimal integer up to 38 digits long, and exponent is an integer from –128 through 127.
You could also parse it as a Double if the full precision is not needed:
struct Foo: Decodable {
let key: Double
}
let coder = JSONDecoder()
let test = try! coder.decode(Foo.self, from: json.data(using: .utf8)!)
print(test) // Foo(key: 1.8446744073709552e+36)
It seems to be the case that JSONDecoder is using NSDecimalNumber behind the scenes
struct Foo: Decodable {
let key: Int
}
// this is 1 + the mantissa of NSDecimalNumber.maximum
let json = "{\"key\":340282366920938463463374607431768211456}"
let coder = JSONDecoder()
let test = try! coder.decode(Foo.self, from: json.data(using: .utf8)!)
Even in the DecodingError, the number is not accurately represented:
Parsed JSON number <340282366920938463463374607431768211450> does not fit in Int.
So use Decimal if you want to be able to decode at maximum precision (though you still might silently lose precision). Otherwise you just need to yell at whoever is sending you that JSON.
Note that while the documentation says
mantissa is a decimal integer up to 38 digits
It's actually a 128-bit unsigned integer, so it can represent some 39 digit numbers as well, as seen above.
I am developing an iOS App that fetches Trivia Questions from Open Trivia Database (API)
After reading the docs and played around with it I think that the best solution is to use base64 encoding (since it seems to be supported in Swift). I have successfully fetched the data and parsed it into structs using a JSONParser. The problem that I have to solve is how to convert the values from base64 to UTF8. (The keys are read correctly, and therefore it maps to my structs)
My first idea was to use decoder.dataDecodingStrategy = .base64, but that does not seem to have any effect at all. And I am not really sure why.
Is that the right way to do it, or should I decode it myself afterwards when the strings are read in to structs?
In short, the result of the Parsing is a struct containing a responseCode as an Int and array containing structs representing the questions with the strings that I want to convert to UTF8 as members
My code for parsing looks like this:
let urlPath = "https://opentdb.com/api.php?amount=10&encode=base64"
let apiURL = URL(string: urlPath)!
URLSession.shared.dataTask(with: apiURL) { (data, response, error) in
guard let data = data else {return}
do{
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64
let questionData = try decoder.decode(Response.self, from: data)
print(questionData)
}catch let err{
print("Error", err)
}
}.resume()
Base64 encoding is used for properties you declared as Data, not as Strings, like so:
struct Response: Codable {
let someBaseEncodedString: Data
var someString: String? {
get {
return String(data: someBaseEncodedString, encoding: .utf8)
}
}
}
So, for the example you are giving, all the properties that are returned as a base64 encoded string should have the Data type in your struct, and then after that decoded as strings.
As suggested by other answers, you can decode Data or Base-64 String after JSONSerialization or JSONDecoder decoded the API results.
But if you prefer to write decoding initializer, you can make it as follows:
This may not be much different from your own Response, I guess.
struct Response: Codable {
var responseCode: Int
var results: [Result]
enum CodingKeys: String, CodingKey {
case responseCode = "response_code"
case results
}
}
To prepare to write a decoding initializer for Response, I would like to use some extensions:
extension KeyedDecodingContainer {
func decodeBase64(forKey key: Key, encoding: String.Encoding) throws -> String {
guard let string = try self.decode(String.self, forKey: key).decodeBase64(encoding: encoding) else {
throw DecodingError.dataCorruptedError(forKey: key, in: self,
debugDescription: "Not a valid Base-64 representing UTF-8")
}
return string
}
func decodeBase64(forKey key: Key, encoding: String.Encoding) throws -> [String] {
var arrContainer = try self.nestedUnkeyedContainer(forKey: key)
var strings: [String] = []
while !arrContainer.isAtEnd {
guard let string = try arrContainer.decode(String.self).decodeBase64(encoding: encoding) else {
throw DecodingError.dataCorruptedError(forKey: key, in: self,
debugDescription: "Not a valid Base-64 representing UTF-8")
}
strings.append(string)
}
return strings
}
}
Using these extensions above, you can define the Result type as follows:
extension Response {
struct Result: Codable {
var category: String
var type: String
var difficulty: String
var question: String
var correctAnswer: String
var incorrectAnswers: [String]
enum CodingKeys: String, CodingKey {
case category
case type
case difficulty
case question
case correctAnswer = "correct_answer"
case incorrectAnswers = "incorrect_answers"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.category = try container.decodeBase64(forKey: .category, encoding: .utf8)
self.type = try container.decodeBase64(forKey: .type, encoding: .utf8)
self.difficulty = try container.decodeBase64(forKey: .difficulty, encoding: .utf8)
self.question = try container.decodeBase64(forKey: .question, encoding: .utf8)
self.correctAnswer = try container.decodeBase64(forKey: .correctAnswer, encoding: .utf8)
self.incorrectAnswers = try container.decodeBase64(forKey: .incorrectAnswers, encoding: .utf8)
}
}
}
(You have not mentioned if your Response (or other name?) is defined as a nested type or not, but I think you can rename or modify it yourself.)
With all things above, you can simply decode the API response as:
do {
let decoder = JSONDecoder()
let questionData = try decoder.decode(Response.self, from: data)
print(questionData)
} catch {
print("Error", error)
}
By the way, you say I think that the best solution is to use base64 encoding (since it seems to be supported in Swift), but is that really true?
Base-64 to Data is supported in JSONDecoder, but it is not what you expect. So, using another encoding can be a better choice.
But, anyway, JSON string can represent all unicode characters using only ASCII with \uXXXX or \uHHHH\uLLLL. So, I do not understand why the API designers do not provide an option Standard JSON Encoding. If you can contact to them, please tell them to provide the option, that may simplify many client side codes.
I'm having problems converting a document to Firebase, but I can not convert the FIRTimestamp data.
let json = try? JSONSerialization.data(withJSONObject: d.data(), options: .prettyPrinted)
Error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (FIRTimestamp)'
To remove FIRTimestamp from JSON
struct leadDocument: Codable {
let state: String
let details: String
}
let dataDescription = document.data() // your json response or value
var leadData = dataDescription
_ = leadData.removeValue(forKey: "serverTimeStamp") // remove FIRTimestamp
let requestData = try! JSONSerialization.data(withJSONObject: leadData, options: JSONSerialization.WritingOptions.prettyPrinted) as NSData?
let results = try JSONDecoder().decode(leadDocument.self, from: requestData! as Data)
OR covert FIRTimestamp to JSON
let db = Firestore.firestore()
let settings = db.settings
settings.areTimestampsInSnapshotsEnabled = true
db.settings = settings
let timestamp: Timestamp = document.get("serverTimeStamp") as! Timestamp
let date: Date = timestamp.dateValue()
print(date)
If you're trying to serialize the contents of a FIRTimestamp, you should either:
Convert it to a NSDate with dateValue, and serialize that instead
Convert it to seconds (and nanoseconds if desired) using the linked methods
When you deserialize those values, you may to convert them back into a FIRTimestamp with one of its constructors.
I'm having to use a FoundationDB database for a project. It stores data as a key:value pair stored as Bytes. I want to store a JSON object mapped to a struct I have. I want to be able save the data as an encoded JSON object and then be able to recreate the JSON object by reading the value Bytes from the DB.
In my CreateRecord function I pass a JSON in a request and use it to create my Country object. I need to convert the type Data into Bytes to store it.
So far I have come up with this.
let data: Bytes = try JSONEncoder().encode(country).base64URLEncodedString()
Then, when I read the record from the database I need to be able to reverse the process to create my Country object from the Bytes that store the JSON.
let mycountry:Country = try decoder.decode(Country.self, from: Data(bytes: DBrecord.value) )
The struct is:
struct Country: Content {
var country_name: String
var timezone: String
var default_PickUp_location: String = ""
init(country_name: String, timezone:String, default_PickUp_location: String?) {
self.country_name = country_name
self.timezone = timezone
if default_PickUp_location != nil {
self.default_PickUp_location = default_PickUp_location!
}
}
}
And a sample JSON is:
{ "country_name" : "Denmark", "timezone" : "Europe\/Copenhagen", "default_pickup_location" : "Copenhagen" }
I cannot seem to be able to reverse the conversion. Any help please?
In your JSON, you use default_pickup_location and in your struct you use default_PickUp_location. You need to settle on one version.
To discover this, I used the following test route:
router.get("json")
{
request throws -> String in
let json = "{ \"country_name\" : \"Denmark\", \"timezone\" : \"Europe/Copenhagen\", \"default_pickup_location\" : \"Copenhagen\" }"
let encoder = try JSONDecoder().decode(Country.self, from: json)
return "It works"
}
It returned:
{"error":true,"reason":"Value required for key
'default_PickUp_location'."}
Where is the type "Bytes" declared, I don't recognize it as a SwiftLang or Foundation Type. I do recoginize Data(bytes: Array<UInt>)? I would guess it is an encoding Decoding type inconsitency. Also for simplifying your encoding and decoding to JSON with out mangling Camel Case in swift you may want to try.
struct Country: Content {
let countryName: String
let timezone: String
let defaultPickupLocation: String
enum CodingKeys: String, CodingKey {
case countryName = "country_name"
case timezone
case defaultPickupLocation = "default_pickup_location"
}
}
I would further recommend that you grab then encoders and decoders from the container for reasons of type consistency. If for example all of your JSON was going to be snake case you could change the encoders and decoders for your container and skip the coding keys enum from above.
var encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
You can get the encoder and decoder from the container as follows
let contentCoders: ContentCoders = try worker.make()
let jsonEncoder = try contentCoders.requireDataEncoder(for: .json) as? JSONEncoder
I'm Having trouble with JSON and Swift 2.
I'm getting this Array from the server
[{"KidName":"Jacob","KidId":1,"GardenID":0},
{"KidName":"Sarah","KidId":2,"GardenID":0},
{"KidName":"Odel","KidId":3,"GardenID":0}]
I'm familiar with JSON and I know it's not the recommended way to get a JSON, since it's supposed to be something like
{"someArray":[{"KidName":"Jacob","KidId":1,"gardenID":0}, .....
So my first question is it possible to run over the first JSON I've post and get the KidName number without editing the JSON and Add to it a JSON OBJECT to hold the array ?
my second question is really with Swift 2, how can I get the KidName (after I've edited the JSON to have an holder for the array)?
this is my code... (please read the Notes I've added)
BTW, I'm familiar with SwiftyJSON as well...
// Method I've build to get the JSON from Server, the Data is the JSON
sendGetRequest { (response, data ) -> Void in
// need to convert data to String So I can add it an holder
if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String {
/**
after editing the str, i'm Having a valid JSON, let's call it fixedJSON
*/
let fixedJSON = "{\"kidsArray\":\(dropLast)}"
// Now I'm converting it to data back again
let jsonTodata = fixedJSON.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// After Having the data, I need to convert it to JSON Format
do{
let dataToJson = try NSJSONSerialization.JSONObjectWithData(jsonTodata, options: []) as! [String:AnyObject]
//Here I'm getting the KidID
if let kidID = jsonSe["kidsArray"]![0]["KidId"]!!.integerValue {
print("kidID in first index is: \(kidID)\n")
}
//NOW trying to get the KidName which not working
if let kidname = jsonSe["kidsArray"]![0]["KidName"]!!.stringValue {
print("KidName is \(kidname)\n")
}
}
So as you can see, I'm not able to get the KidName.
Any Help Would be Appreciate.
You can use the following function to get the 'someArray' array and then use this getStringFromJSON function to get the 'KidName' value.
func getArrayFromJSON(data: NSDictionary, key: String) -> NSArray {
if let info = data[key] as? NSArray {
return info
}
else {
return []
}
}
let someArray = self.getArrayFromJSON(YourJSONArray as! NSDictionary, key: "someArray")
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String {
return info
}
return ""
}
let KidName = self.getStringFromJSON(someArray as! NSDictionary, key: "KidName")
Hope this might be useful to you.