What's the difference between using JSONSerialization and JSONDecoder in swift? - ios

What's the difference between using JSONSerialization and JSONDecoder in swift, while converting JSON to a swift model? It's seems like they are doing the same job. If they do, then when is which to use?
Thank you in advance

Apple has provided JSONDecoder which is a huge relief in swift4 and onwards. We can decode json in just one line. eg
{// sample from quicktype app online
"greeting": "Welcome to quicktype!",
"instructions": [
"Type or paste JSON here",
"Or choose a sample above",
"quicktype will generate code in your",
"chosen language to parse the sample data"
]
}
// MARK: - Welcome
struct Welcome: Codable {
let greeting: String
let instructions: [String]
}
// let welcome = try? newJSONDecoder().decode(Welcome.self, from: jsonData)
Here welcome is the struct which is conforming to codable protocol.
If you want to parse JSON by hand rather than using Codable, iOS has a built-in alternative called JSONSerialization.But i think everybody would like to use JSONDecoder. And also quicktype creates json model classes or struct for u for free.
Check yourself.

Use JSONDecoder !
If I remember well, JSONDecoder appeared in Xcode 9, with Swift4.
It's cleaner and much more efficient to encode and decode JSON.
To do so, your Swift Class has to conform to Decodable Protocol.
JSON Serialization is the Old School. (Like Objective-C)

Related

How can I retrieve a JSON property that has a dash in its name in Swift? [duplicate]

This question already has answers here:
How do I use custom keys with Swift 4's Decodable protocol?
(4 answers)
Closed 6 months ago.
I've just come across a problem in Swift that I haven't found a solution for:
When you decode JSON data (from an API) in Swift, you have to decode it using JSONDecoder, but this forces me to name my variable name exactly like the JSON property that I want to retrieve - the problem is, that the JSON-property's name has a dash in it ("saturated-fat_100g"). Of course, I can't call my variable like that due to the dash. Is there any workaround to this issue?
struct test: Codable {
let testtest: String
let instructions: [String]
enum CodingKeys: String, CodingKey {
case testtest = "test-test"
case instructions
}
}
Here is an example.
you use coding keys to identify the "-" dash.

Implement MCOHTMLRendererIMAPDelegate in MailCore2 using Swift

Recently I've been trying to use MailCore2 inside my react-native app to read mails, so I made a RCTBridge in Swift for it, and I want to return a simple html string of fetched messages... I have successfully fetched messages of type [MCOIMAPMessage] and now I want to map over this array to convert it to html string with the following code
let msgs = (ret.value(forKey: "messages") as! [MCOIMAPMessage]).map{
[
"html": $0.htmlRendering(withFolder: folder as String,
delegate: MCOHTMLRendererIMAPDelegate()) as NSString,
] as NSDictionary
}
Obviously it gives an error, can somebody please tell me the right way to do it...? Or at least tell me how to implement a MCOHTMLRendererIMAPDelegate

Determining Swift Types That Can Be Stored in UserDefaults

I am in the beginning stages of developing an open-source utility for storing state in the Bundle UserDefaults.
I'm encountering an issue when I add non-Codable data types to my Dictionary of [String: Any].
I need to be able to vet the data before trying to submit it, because the UserDefaults.set(_:) method won't throw any errors. It just crashes.
So I want to make sure that the Dictionary that I'm submitting is kosher.
I can't just check if it's Codable, because it can sometimes say that it isn't, when the struct is actually good. (It's a Dictionary<String, Any>, and I can cram all kinds of things in there).
I need to validate that the Dictionary can produce a plist. If this were ObjC, I might use one of the NSPropertyListSerialization methods to test the Dictionary, but it appears as if this set of methods is not available to Swift.
According to the UserDefaults docs, there are a specific set of types and classes that are "plist-studly."
I think testing each type in the list is unacceptable. I need to see if I can find a way to test that won't be screwed the first time Apple updates an OS.
Is there a good way to test a Dictionary<String, Any> to see if it will make UserDefaults.set(_:) puke?
The Property List type set of UserDefaults is very limited. The supported types are
NSString → Swift String
NSNumber → Swift Int, Double or Bool
NSDate → Swift Date
NSData → Swift Data
Arrays and dictionaries containing the 4 value types.
Any is not supported unless it represents one of the 4 value or 2 collection types.
Property List compliant collection types can be written to UserDefaults with PropertyListSerialization (even in Swift).
There are two protocols to serialize custom types to Data
Codable can serialize structs and classes.
NSCoding can serialize subclasses of NSObject.
All types in the structs/classes must be encodable and decodable (means conform to the protocol themselves).
The APIs of PropertyListSerialization / PropertyListEncoder/-Decoder and NSKeyed(Un)Archiver provide robust error handling to avoid crashes.
UPDATE[1]: And, just because I like to share, here's the actual completed project (MIT License, as is most of my stuff)
UPDATE: This is the solution I came up with. Even though I greenchecked vadian's excellent answer, I decided to get a bit more picky.
Thanks to matt pointing out that I was looking under the wrong sofa cushions for the keys, I found the Swift variant of NSPropertyListSerialization, and I use that to vet the top level of the tree. I suspect that I'll need to refactor it into a recursive crawler before I'm done, but this works for now.
Here's the code for the _save() method at the time of this writing. It works:
/* ################################################################## */
/**
This is a private method that saves the current contents of the _values Dictionary to persistent storage, keyed by the value of the "key" property.
- throws: An error, if the values are not all codable.
*/
private func _save() throws {
#if DEBUG
print("Saving Prefs: \(String(describing: _values))")
#endif
// What we do here, is "scrub" the values of anything that was added against what is expected.
var temporaryDict: [String: Any] = [:]
keys.forEach {
temporaryDict[$0] = _values[$0]
}
_values = temporaryDict
if PropertyListSerialization.propertyList(_values, isValidFor: .xml) {
UserDefaults.standard.set(_values, forKey: key)
} else {
#if DEBUG
print("Attempt to set non-codable values!")
#endif
// What we do here, is look through our values list, and record the keys of the elements that are not considered Codable. We return those in the error that we throw.
var valueElementList: [String] = []
_values.forEach {
if PropertyListSerialization.propertyList($0.value, isValidFor: .xml) {
#if DEBUG
print("\($0.key) is OK")
#endif
} else {
#if DEBUG
print("\($0.key) is not Codable")
#endif
valueElementList.append($0.key)
}
}
throw PrefsError.valuesNotCodable(invalidElements: valueElementList)
}
}

How to Store Levels Data

Im creating a game using SpriteKit but I’m not using sks files fore levels. I don’t to enter in details about the idea of the game before I release it but basically each level is auto generated based on a few numbers. So essentially what defines a level would be these numbers I would like to know where I could store this numbers. If I used sks files I would just have a file per level but in this case should I have them sorted in an array of levels? Should the array be in the level selection viewcontroller ? Should it be in a singleton class?
Basically what would be a good way to go about storing these values?
So the levels are auto-generated at runtime?
You could use an array of levels, or a file per level. I would just write them to one or more files in your app's documents directory. (I'd probably use one file per level, just to keep it simple and make it so you can easily add more levels without rewriting the whole game layout file each time.)
If you build your level structures out of scalar types, arrays, and dictionaries, (property list objects) then you can write the "object graph" to a property list using the NSArray or NSDictionary method write(to:).
Alternately you could make your level object conform to the Codable protocol, convert it to JSON, and save the JSON data to a file. The Codable protocol is easy to use, it's well documented by Apple, and there are tons of tutorials online.
EDIT
Note that you could also write your data to a property list using the Codable protocol. Just like the JSONEncoder and JSONDecoder classes, there are PropertyListEncoder and PropertyListDecoder classes that will convert your object graph back and forth to property list format. (Binary properties lists are more compact and faster to read and write than JSON.)
Below is a sample playground that defines a custom struct FooStruct, makes it Codable, and then uses a PropertyListEncoder to write the data to the playground's shared data directory (which you will have to set up if you want to test this code)
import UIKit
import PlaygroundSupport
struct FooStruct: Codable {
let aString: String
let anotherString: String
let anInt: Int
}
let fooArray: [FooStruct] = [FooStruct(aString: "Foo 1",
anotherString: "String 1", anInt: 4),
FooStruct(aString: "Foo 2",
anotherString: "String 2", anInt: 7)
]
let encoder = PropertyListEncoder()
encoder.outputFormat = .binary
do {
print(fooArray)
let data = try encoder.encode(fooArray)
let plistURL = playgroundSharedDataDirectory.appendingPathComponent("property list.plist")
try data.write(to: plistURL)
print("Data written to \(plistURL.path)")
} catch {
print (error)
}

Decoding [[String]] using JSONDecoder?

I have been away from Swift for the last 12 months but have just come back to an application that was previously working (Swift 4.0) but now seems to be failing and I am not sure why.
My problem relates to the JSONDecoder and trying to decode an array of arrays of strings.
var tryingToDecode: [[String]]
The JSON that I am using has the following format (See table below) for the actual data please use the following link.
JASON DATA
The code I am using is (See below) self.requestData is the JSON data I am using, which works when decoding all my other data, it just does not work with [[String]]
func TEST_decodeReceivedJSON() {
let decoder = JSONDecoder()
do {
let array = try decoder.decode(DataStruct.self, from: self.requestData)
print(array)
} catch {
print("Error")
}
}
The struct that I am using for the decode is
struct DataStruct: Codable {
var data: [[String]]
}
This is just test code, but when I compile it I always get thrown to the catch error. I have tried searching online but can't find any relevent examples. The strange thing is that prior to Xcode 10 this worked, it was even accepted in the App Store. I have now been informed by a number of users that something is not working and this is indeed the case, it seems to be related to this particular section where the [[String]] is decoded using DataStruct.
Any help or pointers would be very much appreciated.
[EDIT 001] Added a link to the JSON data, the code below shows a minimal example, I am specifically interested in how I should be accessing the [[String]] -- the array of arrays of strings. I am trying to assertain as this was working before is there something wrong with the way I am trying to decode the JSON (maybe a Swift update/change)or is there maybe a problem with JSONDecoder.
[EDIT 002] The solution was [[String?]] and the problem was indeed in the JSON, you just can't see it in the text blizzard of the raw data, if you look at the table view below you can clearly see that Item 10 is "null" and as a consequence the code required an optional String.
The issue is not with the JSONDecoder, the issue is with your JSON data.
When I checked your code I'm getting the following error:
valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath:
[CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue:
"Index 11", intValue: 11), _JSONKey(stringValue: "Index 10", intValue:
10)], debugDescription: "Expected String but found null value
instead.", underlyingError: nil))
In your code you are expecting string, but when I checked your JSON data it contains null values and it will obviously break the written code. To fix this issue all you have to do is, change your model definition to accept null/nil values:
struct DataStruct: Codable {
var data: [[String?]]
}

Resources