Serialize json for its sample as a string - swift

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"}

Related

CompactMapValues not working on this simple dictionary

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

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)

Decoding this simple JSON struct is not working

I have a simple model which I defined to decode a struct.
But it is failing at decoding.
Can any one tell me what i am doing wrong?
struct Model: Codable {
let firstName: String
let lastName: String
let age: Int
enum Codingkeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let session = URLSession.shared
let url = URL(string: "https://learnappmaking.com/ex/users.json")!
let task = session.dataTask(with: url) { (data, response, error) in
let decoder = JSONDecoder()
let d = try! decoder.decode([Model].self, from: data!) //fails here
print(d)
}
task.resume()
}
}
I double checked to see if the json was correct, but it still fails to decode.
Error shown
Thread 5: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "firstName",
intValue: nil), Swift.DecodingError.Context(codingPath:
[_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No
value associated with key CodingKeys(stringValue: \"firstName\",
intValue: nil) (\"firstName\").", underlyingError: nil))
It keeps searching for firstName but i specifically have a enum to check for first_name.
This is the JSON Payload
[
{
"first_name": "Ford",
"last_name": "Prefect",
"age": 5000
},
{
"first_name": "Zaphod",
"last_name": "Beeblebrox",
"age": 999
},
{
"first_name": "Arthur",
"last_name": "Dent",
"age": 42
},
{
"first_name": "Trillian",
"last_name": "Astra",
"age": 1234
}
]
I know I can add decoder.keyDecodingStrategy = .convertFromSnakeCase but I want to know why the existing code is not working?
The code is correct, but apparently there is some problem with your model (although convertFromSnakeCase does work)
I retyped the struct and the error went away. Please copy and paste this
struct Model : Decodable {
let firstName : String
let lastName : String
let age : Int
private enum CodingKeys : String, CodingKey { case firstName = "first_name", lastName = "last_name", age }
}
Some of the values are optional, to be safe make all let as optional, It will work for sure.
struct Model: Codable {
let firstName: String?
let lastName: String?
let age: Int?
enum Codingkeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age
}
}

Swift Codable Map Children by ID

I have the following code.
import Foundation
let jsonData = """
[
{"id": "1", "firstname": "Tom", "lastname": "Smith", "age": "28"},
{"id": "2", "firstname": "Bob", "lastname": "Smith"},
{"id": "3", "firstname": "Jim", "lastname": "Smith", "parentid": "2"},
{"id": "4", "firstname": "Ray", "lastname": "Smith", "parentid": "3"}
]
""".data(using: .utf8)!
class Person: Codable {
let id: String
let firstName, lastName: String
let age: String?
let parentid: String?
let children: [Person]?
enum CodingKeys : String, CodingKey {
case firstName = "firstname"
case lastName = "lastname"
case age
case parentid
case id
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
firstName = try values.decode(String.self, forKey: .firstName)
lastName = try values.decode(String.self, forKey: .lastName)
age = try values.decodeIfPresent(String.self, forKey: .age)
parentid = try values.decodeIfPresent(String.self, forKey: .parentid)
id = try values.decodeIfPresent(String.self, forKey: .id)
}
}
let decoded = try JSONDecoder().decode([Person].self, from: jsonData)
print(decoded)
So I have that children property. I basically want decoded to be an array with 2 Person objects (Tom and Bob). decoded[1].children should be an array with 1 Person object (Jim). And decoded[1].children[0] should be an array with 1 Person object (Ray).
How can I achieve this using the Swift Codable system?
You can do something like:
if let decoded = try? JSONDecoder().decode([Person].self, from: jsonData)
{
for person in decoded
{
person.children = decoded.filter({ (child) -> Bool in
return person.id == child.parentid
})
}
}
In the above code, after getting decoded array I just traversed it using a for loop to identify the children of each person if exist.