Decode Arbitrary JSON in Swift - swift

I am trying decode a JSON with below structure and convert to a Swift struct object.
"{\"createdAt\":\"2021-07-20T06:53:05.755Z\",\"extraPayload\":\"32bd7d6691964519\",\"messageID\":\"a5f77d0a27da2e14a78ce4d736590bb3c369d5e5bebd36339553e3b699b82d13\",\"publishedAt\":\"2021-07-20T06:53:05.836655Z\"}"
Both the publishedAt and messageID fields are fixed and any other fields in the JSON can be arbitrary based on a particular use-case.
In this case both fields createdAt and extraPayload are completely optional and another case it could be something like appConfig:true etc apart from the 2 fixed fields.
How can I map into a struct with below format which has 2 mandatory fixed fields and arbitrary part captured using a Dictionary of type [String:Any]?
public struct MessagePayload {
/**
* Message identifier — opaque non-empty string.
*/
let messageID: String
/**
* ISO 8601 date indicating a moment when the message was published to the ACN back-end service.
*/
let publishedAt: String
/**
* Application-specific fields.
*/
let messageObject: [String:Any]?
}
I have tried using Swift Codable but it does not support [String:Any].

It seems like JSONSerialization from Foundation is probably a better fit for your use case. This will decode the JSON data directly into an Any type, which you can then cast to [String: Any] to access the underlying properties.
let decoded = try? JSONSerialization.jsonObject(with: myJsonData, options: []) as? [String: Any]
decoded?["messageID"]
decoded?["publishedAt"]
// ...access other properties as needed

Related

String as Member Name in Swift

I have an array of strings and a CoreData object with a bunch of variables stored in it; the strings represent each stored variable. I want to show the value of each of the variables in a list. However, I cannot find a way to fetch all variables from a coredata object, and so instead I'm trying to use the following code.
ListView: View{
//I call this view from another one and pass in the object.
let object: Object
//I have a bunch of strings for each variable, this is just a few of them
let strings = ["first_name", "_last_name", "middle_initial" ...]
var body: some View{
List{
ForEach(strings){ str in
//Want to pass in string here as property name
object.str
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
}
}
}
So as you can see, I just want to pass in the string name as a member name for the CoreData object. When I try the code above, I get the following errors: Value of type 'Object' has no member 'name' and Expected member name following '.'. Please tell me how to pass in the string as a property name.
CoreData is heavily based on KVC (Key-Value Coding) so you can use key paths which is much more reliable than string literals.
let paths : [KeyPath<Object,String>] = [\.first_name, \.last_name, \.middle_initial]
...
ForEach(paths, id: \.self){ path in
Text(object[keyPath: path]))
}
Swift is a strongly typed language, and iterating in a python/javascript like approach is less common and less recommended.
Having said that, to my best knowledge you have three ways to tackle this issue.
First, I'd suggest encoding the CoreData model into a dictionary [String: Any] or [String: String] - then you can keep the same approach you wanted - iterate over the property names array and get them as follow:
let dic = object.asDictionary()
ForEach(strings){ str in
//Want to pass in string here as property name
let propertyValue = dic[str]
//This doesn't work because string cannot be directly passed in as property name - this is the essence of my question.
}
Make sure to comply with Encodable and to have this extension
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
Second, you can hard coded the properties and if/else/switch over them in the loop
ForEach(strings){ str in
//Want to pass in string here as property name
switch str {
case "first_name":
// Do what is needed
}
}
Third, and last, You can read and use a technique called reflection, which is the closest thing to what you want to achieve
link1
link2

Swift Userdefaults converting String to __NSCFString

I have code that save a dictionary of [String: Any] in UserDefaults. On retrieval String are changed to __NSCFString. I am using Mixpanel to track events and sends this dictionary as events properties. Now the problem is __NSCFString is not a valid MixpanelType so Mixpanel is discarding my dictionary.
Questions:
Is there a way to get same datatypes that are saved using dictionary in UserDefaults?
Is there a way Mixpanel accepts converted datatypes?
Here is a code I am using
var mixpanelProperties: [String: Any] {
get { defaults.dictionary(forKey: "\(#function)") ?? [:] }
set { defaults.set(newValue, forKey: "\(#function)") }
}
mixpanelProperties = ["a-key": "value for the key"]
let prop = mixpanelProperties
print("Type of: \(String(describing: prop["a-key"]))")
asdad
MacOS and iOS use a variety of different classes to represent strings, which are all compatible. x as? String should just work, no matter what the concrete class of x is. Unless you use code that explicitely checks the class which you shouldn't do.

Convert date format from json data in the model and then pass to controller

Here in my model I have a few struct to handle the data from server
struct Articles: Decodable {
var content : [ArticleData]
}
struct ArticleData: Decodable {
var title: String
var date_time: Date
var image: String
}
From the jason, I get a date, it's like that:
"date_time": 1524842056.035,
First question:
I set the type of this type in model Date as you see in ArticleData, it that correct?
Then I read this json in the model something like that:
guard let json = try? JSONDecoder().decode(Articles.self, from: data!) else {
completion(.failure(loginError.networkError))
return
}
completion(.success(json.content))
As you can see, I get an array from server json.content that contain title, date_time and image. I want to convert it here in the model and pass everything in completion to the controller.
could you help me on that?
Thanks :)
you can use type 'String' for your date and then convert it using dateFormatter or, use 'dateDecodingStrategy' function in your decoder.
for passing the data you are already doing the correct thing. where ever you call the method you can get the model.

Swift - Is there a way to differentiate between a field not being present or a field being nil/null when decoding an optional Codable value

The Necessary Functionality
I'm in the process of modifying a system to save a queue of currently unsent API requests to UserDefaults to be re-sent when the user's connection allows.
As some patch requests require the ability to send an actual NULL value to the API (and not just ignore out the field if its a nil optional), this means I need the ability to encode and decode nil/NULL values from defaults for certain fields.
The Issue
I have the encoding side down, and can happily encode requests to either send NULL fields to the server or encode them to Defaults. However, my issue is that when it comes to decoding saved unsent requests, I can't find a way to differentiate between an actual Nil value and the field just not being there.
I am currently using decodeIfPresent to decode my fields (all of the fields for these requests are optional), which returns nil if the field is empty OR if the field is set to Nil/NULL. Obviously this doesn't work for my fields that can explicitly be set to Nil, as there is no way for me to differentiate between the two cases.
Question
Is there any decode methodology I could implement that would allow for differentiating between a field not being there and a field actually being set to nil?
There is no way , but you can add another info to know that
struct Root : Codable {
let code : Int?
let codeExists:Bool?
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
code = try values.decodeIfPresent(Int.self, forKey: .code)
codeExists = values.contains(.code)
}
}
According to docs decodeIfPresent
This method returns nil if the container does not have a value associated with key, or if the value is null. The difference between these states can be distinguished with a contains(_:) call.
So decoding
let str = """
{
"code" : 12
}
"""
gives
Root(code: Optional(12), codeExists: Optional(true))
&&
This
let str = """
{
"code" : null
}
"""
gives
Root(code: nil, codeExists: Optional(true))
and this
let str = """
{
}
"""
gives
Root(code: nil, codeExists: Optional(false))

Can Codable APIs be used to decode data encoded with NSKeyedArchiver.archivedData?

I'm converting a codebase from using NSCoding to using Codable. I have run into an issue when trying to restore data encoded with NSCoding. I have an object that was encoded with the code:
let encodedUser = NSKeyedArchiver.archivedData(withRootObject: user)
let userDefaults = UserDefaults.standard
userDefaults.set(encodedUser, forKey: userKey)
userDefaults.synchronize()
It was previously being decoded with the code:
if let encodedUser = UserDefaults.standard.data(forKey: userKey) {
if let user = NSKeyedUnarchiver.unarchiveObject(with: encodedUser) as? User {
\\ do stuff
}
}
After updating the User object to be Codable, I attempted to update the archive/unarchive code to use PropertyListDecoder because I saw that archivedData(withRootObject:) returns data formatted as NSPropertyListBinaryFormat_v1_0. When running this code:
if let encodedUser = UserDefaults.standard.data(forKey: userKey) {
let user = try! PropertyListDecoder().decode(User.self, from: encodedUser)
\\ do stuff
}
I get a keyNotFound error for the first key I'm looking for, and breakpointing in my init(from: Decoder), I can see that the container has no keys. I also tried to use PropertyListSerialization.propertyList(from:options:format) to see if I could pass that result into PropertyListDecoder, but that function gave me an NSCFDictionary, which is structured in a really strange way but does appear to contain all the data I'm looking for.
So is it possible to decode an object using Codable APIs if it was encoded with NSKeyedArchiver.archivedData(withRootObject:)? How can I achieve this?
I decided that the answer is almost certainly no, you cannot use Codable to decode what NSCoding encoded. I wanted to get rid of all the NSCoding cruft in my codebase, and settled on a migration path where I have both protocols implemented in the critical data models to convert stored data from NSCoding to Codable formats, and I will remove NSCoding in the future when enough of my users have updated.