UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Day 61 Challenge - Anyone successfully download JSON and store it in Core Data?

Forums > 100 Days of SwiftUI

Woe is me! I am completely stuck on this challenge...

My data was originally stored in a struct User, with a nested struct Friend.

When I created a Core Data database, I created two entities, User and Friend. I gave User an attribute of 'friends' with type of Transformable and gave it a custom class of type [Friend]. I did not use any relationships. I set User and Friend to None/Manual and then created the NS ManagedObject subclasses.

After adding Codable to both classes, I ended up with code that looked like this:

User:

import Foundation
import CoreData

@objc(User)
public class User: NSManagedObject, Codable {

    enum CodingKeys: CodingKey {
        case id, isActive, name, age, company, email, address, about, registered, tags, friends
    }

    required convenience public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.init()

        try id = container.decode(String.self, forKey: .id)
        try isActive = container.decode(Bool.self, forKey: .isActive)
        try name = container.decode(String.self, forKey: .name)
        try age = container.decode(Int16.self, forKey: .age)
        try company = container.decode(String.self, forKey: .company)
        try email = container.decode(String.self, forKey: .email)
        try address = container.decode(String.self, forKey: .address)
        try about = container.decode(String.self, forKey: .about)
        try registered = container.decode(Date.self, forKey: .registered)
        try tags = container.decode([String].self, forKey: .tags)
        try friends = container.decode([Friend].self, forKey: .friends)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(id, forKey: .id)
        try container.encode(isActive, forKey: .isActive)
        try container.encode(name, forKey: .name)
        try container.encode(age, forKey: .age)
        try container.encode(company, forKey: .company)
        try container.encode(email, forKey: .email)
        try container.encode(address, forKey: .address)
        try container.encode(about, forKey: .about)
        try container.encode(registered, forKey: .registered)
        try container.encode(tags, forKey: .tags)
        try container.encode(friends, forKey: .friends)
    }

}

Friend

import Foundation
import CoreData

@objc(Friend)
public class Friend: NSManagedObject, Codable {
    enum CodingKeys: CodingKey {
        case id, name
    }

    required convenience public init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.init()

        try id = container.decode(String.self, forKey: .id)
        try name = container.decode(String.self, forKey: .name)

    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
    }
}

Am I on the right track?

In my contentView, do I need to load the JSON data into my old struct first, or can I decode it directly into my new Class, like this?

extension ContentView {
    func getUsers() {
        guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {
            return
        }
        var request = URLRequest(url: url)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "GET"

        URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data else {
                print("No data in reponse: \(error?.localizedDescription ?? "Unknown error")")
                return
            }

            //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            //setting up dateFormat...The reason it continued to fail over and over again!
            //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            let decoder = JSONDecoder()
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
            decoder.dateDecodingStrategy = .formatted(formatter)
            //decoder.keyDecodingStrategy = .convertFromSnakeCase

            if let decodedData = try? decoder.decode([User].self, from: data) {
                //Do something with decodedData: [User]???
                }
        }
        .resume()
    }
}

If you completed this Challenge and want to share how you did it, that would be fantastic.

3      

Yeah i tried to parse the JSON straight into Core Data but i couldn't make my Entities conform to Codable based on the NSSet that is created when i create a relationship of one to many. I have been trying to find an answer but i haven't been able to so far. I have just ended up parsing it into the old structs from challenge 60 and then putting into Core Data. A lot of work. I would also like to know if anyone has successfully parsed it straight into Core Data.

4      

Thanks for the reply Newy!

I too have moved on from trying to parse the JSON directly into my Core Data entities. No matter what I did, I found that editing my NSManagedObject subclasses for User and Friend to make the codable resulted in the app crashing...

So as I move on with the next set of days (on Day 63 today) I'm going to work on doing the challenge as you did... that is, decoding the JSON into my structs and then writing some function to write that data into my Core Data through some managed object context (moc).

In my searching for answers, I found two other people who followed this method successfully and are good resources for people struggling:

Jules Lee

Peter Barclay

Although, I find it strange that neither of them seem to have tackled the question of how to write in tags, which in our structs is a simple array of String.

In my tinkering, I ended up setting the tags attribute in my Core Data .xcdatamodeld file to Transformable, and then in the Data Model Inspector, setting the Custom Class to [String]. Whether or not this will work... I'll report back later.

3      

Thanks for the reply Zoyd, let me know how you go with that Custom class for the tags.

3      

Well, I was able to get it working today. There was some strange stuff with Core Data that would cause the app to crash, and once that happened, no amount of CTRL+Z would get it to stop crashing, so I had to scrap several projects and start over. It might've had to to with changing attributes/entities in Core Data after writing to Core Data, but I'll try to figure that out later.

The custom Class for the tags attributed worked suprisingly well. It was as simple as assigning the tags attribute with the [String] property from my User Struct.(see code below)

In my code, I use .onAppear in ContentView to call fetchJSON, which parses the JSON into my User Struct, then passes that [User] into my saveToCD function.

func saveToCD(users: [User]) {
        users.forEach { user in
            let person = CDUser(context: moc)
            person.id = user.id
            person.name = user.name
            person.isActive = user.isActive
            person.age = Int16(user.age)
            person.company = user.company
            person.email = user.email
            person.address = user.address
            person.about = user.about
            person.registered = user.registered
            person.tags = user.tags

            var tempFriendArray = Array<CDFriend>()

            user.friends.forEach { friend in
                let cdFriend = CDFriend(context: moc)
                cdFriend.id = friend.id
                cdFriend.name = friend.name
                tempFriendArray.append(cdFriend)
            }

            person.friends = NSSet(array: tempFriendArray)
        }
        if moc.hasChanges {
            try? moc.save()
        }
    }

Maybe there's a better solution to the Friends array, but this is what finally got everything showing up properly.

Using Date and UUID for the registered and id attributes, respectively, also posed no problems. For 'friends' data, I just made a many-to-many relationship between my User and Friend entities. Both CDFriend and CDUser have constraints on id.

4      

awesome job. ill give that a try. im working on my own app at the moment but ill be revisiting this to see if it can optimised anymore. Might wait till WWDC to see what changes or optimisations are made to swift ui and core data.

3      

I'll have to try it like this as well... putting it into structs and then converting the structs to Core Data entities. There must be a way to do it directly, but I can't wrap my head around how it's supposed to happen.

3      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.