CompactMapValues not working on this simple dictionary - swift

I have a model that looks like:
public struct Profile {
public let bio: String?
public let company: String
public let createdDate: Date
public let department: String
public let email: String
public let firstName: String
public let coverImageURL: URL?
public let jobTitle: String
public let lastName: String
public let location: String?
public let name: String
public let profileImageURL: URL?
public let roles: [String]
public let isActive: Bool
public let updatedDate: Date?
public let userID: String
public init(
bio: String?, company: String, createdDate: Date, department: String, email: String, firstName: String, coverImageURL: URL?, jobTitle: String,
lastName: String, location: String?, name: String, profileImageURL: URL?, roles: [String], isActive: Bool, updatedDate: Date?, userID: String) {
self.bio = bio
self.company = company
self.createdDate = createdDate
self.department = department
self.email = email
self.firstName = firstName
self.coverImageURL = coverImageURL
self.jobTitle = jobTitle
self.lastName = lastName
self.location = location
self.name = name
self.profileImageURL = profileImageURL
self.roles = roles
self.isActive = isActive
self.updatedDate = updatedDate
self.userID = userID
}
}
extension Profile: Equatable { }
I am trying to create a tuple that represents it as (model: Profile, json: [String: Any])
using the following method -
func makeProfile() -> (model: Profile, json: [String: Any]) {
let updatedDate = Date()
let updatedDateStr = updatedDate.toString(dateFormat: "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX")
let createdDate = Date()
let createdDateStr = createdDate.toString(dateFormat: "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX")
let coverImageURL = makeURL("https://headerUri.com")
let profileImageURL = makeURL("https://pictureUri.com")
let model = Profile(
bio: "Some Bio",
company: "Cool Job INC",
createdDate: createdDate,
department: "Engineering",
email: "name#domain.tld",
firstName: "Anne",
coverImageURL: coverImageURL,
jobTitle: "Test Dummy",
lastName: "Employee",
location: "London",
name: "Anne Employee",
profileImageURL: profileImageURL,
roles: ["ADMIN"],
isActive: true,
updatedDate: updatedDate,
userID: UUID().uuidString
)
let json: [String: Any] = [
"bio": model.bio,
"company": model.company,
"createdDate": createdDateStr,
"department": model.department,
"email": model.email,
"firstName": model.firstName,
"headerUri": model.coverImageURL?.absoluteString,
"jobTitle": model.jobTitle,
"lastName": model.lastName,
"location": model.location,
"name": model.name,
"pictureUri": model.profileImageURL?.absoluteString,
"roles": model.roles,
"isActive": model.isActive,
"updatedDate": updatedDateStr,
"userId": model.userID
].compactMapValues { $0 }
return (model: model, json: json)
}
}
extension Date {
func toString(dateFormat format: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.string(from: self)
}
}
All of the optional properties are showing a warning Expression implicitly coerced from 'String?' to 'Any'
I have added .compactMapValues { $0 } to the dict but has had no effect.
How can I clear this warning?

You're getting an error because you trying to implicitly coerce an optional value (which could be nil) to Any, which hides that the value could be nil.
compactMapValues doesn't guarantee at compile-time that the value is not nil; all the compiler knows is that something that is an optional (like String?) is now treated as a non-optional Any.
One way you could avoid the error is by providing a default value instead of the optional:
let json: [String: Any] = [
"bio": model.bio ?? ""
// ...
]
Alternatively, you can create a dictionary of: [String, Any?] to assign values to, and then use .compactMapValues, which would give you back non-optional values of Any.
let withOptionals: [String: Any?] = [
"bio": model.bio,
// ...
]
let json: [String, Any] = withOptionals.compactMapValues { $0 }

You're getting the warning because the values are optional when you initialize the dictionary. Your .compactMapValues { $0 } won't help because by then it's "too late".
Instead you could initialize an empty dictionary and add the values 1 by 1.
var json = [String: Any]()
json["bio"] = model.bio
json["company"] = model.company
// all of the other values
json["userId"] = model.userID

Related

Serialize json for its sample as a string

i'm trying to serialize a json but i have an error could someone give me a guide please i'm doing wrong i'm new to swift[enter image description here][1]
let code = "00001"
let firstName = "Joe"
let lastName = "Doe"
let middleName = "Mc."
let age = 100
let weight = 45
let jsonObject: [String: [String:Any]] = [
"code": code, <---- Cannot convert value of type 'String' to expected dictionary value type '[String : Any]'
"attributeMap": [
"first_name": firstName,
"middle_name": middleName,
"last_name": lastName,
"age": age,
"weight": weight
]
]
if let data = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted),
let str = String(data: data, encoding: .utf8) {
print("===> \(str)")
}
--I would like a result like this JSON format
{
"code": "00001",
"attributeMap": {
"first_name": "Joe",
"middle_name": "Mc",
"last_name": "Doe",
"age": "23",
"weight": "home.zul"
}
}
If it repeatable operation I will prefer to use more "swifty" way. Let's define two structs
Person
struct Person: Encodable {
// Keys
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case middleName = "middle_name"
case lastName = "last_name"
case age
case weight
}
// Properties
let firstName: String
let middleName: String
let lastName: String
let age: Int
let weight: Double
}
The next one
Entity
struct Entity: Encodable {
// Keys
enum CodingKeys: String, CodingKey {
case code
case person = "attributeMap"
}
// Properties
let code: String
let person: Person
}
Usage
let person = Person(
firstName: "Joe",
middleName: "Mc.",
lastName: "Doe",
age: 100,
weight: 45
)
let entity = Entity(
code: "200",
person: person
)
if
let data = try? JSONEncoder().encode(entity),
let json = String(data: data, encoding: .utf8)
{
// Do something
}
Result
{"attributeMap":{"age":100,"last_name":"Doe","middle_name":"Mc.","weight":45,"first_name":"Joe"},"code":"200"}

How to unit test Date in Swift

If I have ArticleItem struct like this:
public struct ArticleItem: Equatable {
public let id: UUID
public let description: String?
public let location: String?
public let thumbnailURL: URL
public let created: Date
public init(id: UUID, description: String?, location: String?, thumbnailURL: URL, created: Date) {
self.id = id
self.description = description
self.location = location
self.thumbnailURL = thumbnailURL
self.created = created
}
}
But from back-end we would receive such info:
{
"id": "a UUID",
"description": "a description",
"location": "a location",
"image": "https://a-image.url",
"created_date": "2020-05-09",
}
How can I test the correctness of Date ?
In the test I have something like this:
func test_load_deliversItemsOn200HTTPResponseWithJSONItems(){
let (sut, client) = makeSUT()
let item1 = ArticleItem(id: UUID(), description: nil, location: nil, thumbnailURL: URL(string: "http://a-url.com/")!, created: Date())
let item1JSON = [
"id": item1.id.uuidString,
"image": item1.imageURL.absoluteString,
"created_date": ??? // what should be here
]
let items = [
"items": [item1JSON]
]
expect(sut: sut, toCompleteWithResult: .success([item1])) {
let itemsJSON = try! JSONSerialization.data(withJSONObject: items)
client.complete(withStatusCode: 200, data: itemsJSON)
}
}
I've heard about using iso8601 when decoding but still don't know how to do it.
Please help me. Thanks
You need to tell the decoder what dateDecoderStrategy your model or json follows.
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(TheFormatThatTheDateHas)
and then
decoder.decode........
for the format, google the date plus with format.

How to model a Swift dictionary property on a Realm object?

How should I model a dictionary property on a Realm object so when encoded to JSON I can get this:
{
"firstName": "John",
"lastName": "Doe",
"favoriteThings": {
"car": "Audi R8",
"fruit": "strawberries",
"tree": "Oak"
}
}
I tried creating a new Object FavoriteThings with 'key' and 'value' properties as I've seen elsewhere...
public class Person: Object {
#objc dynamic var firstName = ""
#objc dynamic var lastName = ""
var favoriteThings = List<FavoriteThings>()
}
But List gives me an array, naturally, when I encode it to JSON. I don't want an array. I'm using Swift Codable.
{
"firstName": "John",
"lastName": "Doe",
"favoriteThings": [
{
"key": "fruit",
"value": "strawberries"
},
{
"key": "tree",
"value": "Oak"
}
],
}
Any pointers appreciated!
Gonzalo
As you know, Lists are encoded into json arrays by default. So, to encode a List into a Dictionary you'll have to implement a custom encode method to do exactly that.
public class FavoriteThings: Object {
#objc dynamic var key = ""
#objc dynamic var value = ""
convenience init(key: String, value: String) {
self.init()
self.key = key
self.value = value
}
}
public class Person: Object, Encodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
case favoriteThings
}
#objc dynamic var firstName = ""
#objc dynamic var lastName = ""
let favoriteThings = List<FavoriteThings>()
convenience init(firstName: String, lastName: String, favoriteThings: [FavoriteThings]) {
self.init()
self.firstName = firstName
self.lastName = lastName
self.favoriteThings.append(objectsIn: favoriteThings)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
var favThings: [String: String] = [:]
for thing in favoriteThings {
favThings[thing.key] = thing.value
}
try container.encode(favThings, forKey: .favoriteThings)
}
}
And the usage would be like this:
func testEncode() {
let john = Person(
firstName: "John",
lastName: "Doe",
favoriteThings: [
.init(key: "car", value: "Audi R8"),
.init(key: "fruit", value: "strawberries"),
.init(key: "tree", value: "Oak"),
])
let encoder = JSONEncoder()
let data = try! encoder.encode(john)
if let string = String(data: data, encoding: .utf8) {
print(string)
}
}
Which prints:
{"firstName":"John","favoriteThings":{"car":"Audi R8","fruit":"strawberries","tree":"Oak"},"lastName":"Doe"}

Swift: How to convert Array of Dictionary to structure?

I have:
array = [["name": String, "lastName": String],
["name": String, "lastName": String],
["name": String, "lastName": String]]
(a: [Сlass.[String:String]]) -> [Class.SomeStruct] {}
How to make a structure with its properties from this array?
Like this:
struct SomeStruct {
let name: String
let lastName: String
}
You can use map or compactMap to transform your dictionaries into structs.
let array = [["name": "String", "lastName": "String"],
["name": "String", "lastName": "String"],
["name": "String", "lastName": "String"]]
struct SomeStruct {
let name: String
let lastName: String
}
let values = array.compactMap { data -> SomeStruct? in
guard let name = data["name"], let lastName = data["lastName"] else {
return nil
}
return SomeStruct(name: name, lastName: lastName)
}
Note: The compactMap will silently ignore all dictionaries that doesn't contains a name or a lastName key.
Use Codable
// MARK: - SomeStructElement
struct SomeStructElement: Codable, Equatable {
let name: Int?
let lastName: String?
enum CodingKeys: String, CodingKey {
case name = "name"
case lastName = "lastName"
}
}
And then use JSONDecoder:
let someStructArray: Array<SomeStructElement> = try? JSONDecoder().decode(Array<SomeStructElement>.self, from: data) ?? []
Source: Encoding and Decoding Custom Types (Apple developer)

Xcode Error "Expected '{' in body of function declaration"

I have tried everything and I cannot figure out the error.
import Foundation
import Firebase
struct User {
var email: String!
var firstname: String
var lastname: String
var uid: String!
var profilePictureUrl: String
var country: String
var ref: FIRDatabaseReference!
var key: String = ""
init(email: String, firstname: String, lastname: String, uid: String, profilePictureUrl: String, country: String) {
self.email = email
self.firstname = firstname
self.lastname = lastname
self.profilePictureUrl = profilePictureUrl
self.country = country
self.ref = FIRDatabase.database().reference()
}
func toAnyObject() -> [String: Any] {
return ["email": email, "firstname": firstname, "lastname": lastname, "country": country, "uid": uid, "profilePictureUrl": profilePictureUrl]
}
}
The error is in the function toAnyObject.
Your function return [String : Any]. However, Its value is not Any.It is possible to edit temporarily as easy to understand as follows.
func toAnyObject() -> [String: Any] {
return ["email": email, "firstname": firstname, "lastname": lastname, "country": country, "uid": uid, "profilePictureUrl": profilePictureUrl] as [String : Any]
Hope it can help you fix issuse.