WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: Update records in json array

Forums > Swift

Hi All,

I hope your all well.

Over the past few days I have tried and failed to work out to update my array containing decoded JSON, I have all the structs setup as seen in many of Pauls tutorials however I am struggling to find a way to update the in memory decoded JSON array as no matter how I type case the array or use key value dictionary pairs in the array Swift keeps saying it doesn't conform to my struct. My question to you all is, is there a general rule of thumb for this operation? I have one textfield that contains the value i need to append to the in memory array of names array.

Thanks for any help!

1      

Please post some code so we can see what you are trying to do, what you've tried, and offer solutions.

1      

Of course! This kind of question has been asked and answered several times in these forums.

Typos, are the most common problems. But without your source code, what can we do other than to say, try again!

Help us to help you. Post some code snips of your structures. Use generous comments so we can see where you deviated from @twostraws lessons. Let us know what your thougths were and why you deviated.

We don't need to see all your VIEW code. Just the decoding stuff and structs.

Have you looked at the other messages in these forums for advice? Which messages did you read that were similar to your questions? Did the answers help clarify your code any?

Please comment your code!

1      

Hacking with Swift is sponsored by Emerge

SPONSORED Why are Swift reference types bad for app startup time, and what’s the performance cost of protocol conformances? That’s just a couple of the topics you can learn about on the Emerge blog — written by the app performance experts behind Emerge’s advanced app optimization and monitoring tools, based on their experience of working at companies like Apple, Airbnb, Snap, and Spotify.

Find out more

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

Hi Both,

Many thanks for you reply, I was at work when I posted this post but now as I am back I can post my playground code below. I have typecast newData as [String: Any], [peopleData2.messages] (which is what xCode says i'm not conforming too and I have even used key and dictionary pairs too, this seems so trivial but I have searched the forums for answers and I haven't come up with a solution, the closest I found was this post: https://www.hackingwithswift.com/forums/swift/problem-adding-another-entry-to-json-file/816/827 however my JSON is already loaded and at the moment I am just trying to add more data to the loaded array for encoding later.

I have learnt alot from these forums and Pauls videos too and I am really trying to hammer down on the absolute basics before devloping my own app in the future so all this is me trying to learn in a way I think will help in the future as in an ideal world I will be exporting things like this to a web database etc but all of this is just me trying to learn the basics.

Thanks all for your help, and for your patience it does mean a lot to me and I apologise for being such a noob, swift is great to use but hard to work out whats gone wrong and why.

class People2: ObservableObject, Codable {
    var people: [peopleData2.person]

    init() {
        do {
            let url = Bundle.main.url(forResource: "smallJSNewFormat4", withExtension: "json")!

            let data = try Data(contentsOf: url)

            let peopleD = try JSONDecoder().decode(People2.self, from: data)

            people = peopleD.people
            //messages = peopleD.people.person

            //let content = people.messages.content

        } catch DecodingError.keyNotFound(let key, let context) {
            fatalError("Failed to decode from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)")
        } catch DecodingError.typeMismatch(_, let context) {
            fatalError("Failed to decode from bundle due to type mismatch – \(context.debugDescription)")
        } catch DecodingError.valueNotFound(let type, let context) {
            fatalError("Failed to decode from bundle due to missing \(type) value – \(context.debugDescription)")
        } catch DecodingError.dataCorrupted(_) {
            fatalError("Failed to decode from bundle because it appears to be invalid JSON")
        } catch {
            fatalError("Failed to decode from bundle: \(error.localizedDescription)")
        }
    }
}

// sample of my internal JSON, this is already decoded earlier in my playground.
{
  "people": [
    {
      "id": "96CA73E5-6B81-49B3-9B35-FC576B74B25D",
      "name": "Helen",
      "age": "25",
      "phone_number": "01002331167",
      "messages": [
        {
          "number": 1,
          "sender": "Helen",
          "content": "Est amet quia optio nobis vero qui dolorum distinctio sint accusamus quis velit dignissimos. Et maiores earum velit odit ducimus non enim ut recusandae sunt. Ut eos aut ipsa ut natus porro et. Molestias laboriosam atque provident voluptatem."
        }
      ]
    }
  ]
}
// decoding and using this works fine and I have just took the first snippit there is more

struct peopleData2: Codable {
    var people: [person]

    struct person: Codable, Identifiable {
        var id: UUID
        let name: String
        let age: String
        let phone_number: String
        var messages: [messages]
    }

    struct messages: Codable, Identifiable {
        var id: Int { number }
        var number: Int
        var sender: String
        var content: String
    }
}

var people2 = People2()
//var msgsOnly: [peopleData2.messages]

print(people2.people.count)

print(people2.people[0].messages.count)

//print(people2.people[0].messages[0])
//print(msgsOnly)

let newData = """
    {
        "number": 8,
        "sender": "Helen",
        "content": "Test"
    }
"""

//let newData = ["number": 8, "sender": "Helen", "content": "Test"]

//let loadData = Data(newData.utf8)

//let newData = ["content": "Test"]
//newData.map({ $0.number; $0.sender; $0.content})

//print(JSONEncoder().encode(newData))

print(newData.count)

//print(loadData)

//msgsOnly.append(contentsOf: newData(number: 8))

people2.people[0].messages[0].content.append(newData)

print(people2.people[0].messages.count)

1      

You need to decode newData into a peopleData2.messages struct and then you can append it to people2.people[0].messages.

let people = People2()
print(people.people)

let newData = """
    {
        "number": 8,
        "sender": "Helen",
        "content": "Test"
    }
"""

if let newMessage = try? JSONDecoder().decode(peopleData2.messages.self, from: newData.data(using: .utf8)!) {
  people.people[0].messages.append(newMessage)
  print(people.people)
}

2      

And just as a bit of advice...

In Swift, the convention is to name types with a capital letter and variables with a lowercase letter. This helps distinguish the two and makes it easier to tell what's going on when reading someone else's—or even your own—code.

Additionally, a struct should be named in the singular since it represents one item. And then is you have an array of those items, name your variable in the plural.

To conform with the convention, you should make the following change to your original code (and adjust the rest of your code where these data structures are referenced accordingly):

struct PeopleData2: Codable {
    var people: [Person]

    struct Person: Codable, Identifiable {
        var id: UUID
        let name: String
        let age: String
        let phoneNumber: String // note 1
        var messages: [Message]
    }

    struct Message: Codable, Identifiable {
        var id: Int { number }
        var number: Int
        var sender: String
        var content: String
    }
}
  1. Swift variables tend to use camel cased names (e.g., phoneNumber) instead of snake cased names (e.g., phone_number). JSON tends to use snake case. You can fix this discrepancy by 1) setting up a CodingKeys enum in your Codable type, or 2) setting decoder.keyDecodingStrategy = .convertFromSnakeCase on your JSONDecoder before decoding your data, like so:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let peopleD = try decoder.decode(People2.self, from: data)

I would also suggest rethinking some of your names. People2 and PeopleData2 just aren't very good names and don't convey what the data is very well. Especially when you start doing things like people2.people and the like. Can get confusing.

2      

Really? Why on earth did I not think of that, so basically the decode approach decodes a "JSON" object from my new entry to then append it as such to the existing Array containing JSON, I guess this will need to be done every time?

Honestly I can't believe the solution could be and is that simple, I've literally spent hours searching for an answer trying everything to work out how on earth I can conform to the struct types when to the naked eye I do, thanks @roosterboy

Thanks for the tips about my code too, I have been watching Pauls videos and I thought "I'm going to need to clean up my code" because it is a mess and your right its hard to read even for me which only adds issues, the 2 part of the name originates from keeping my existing code in my Swift files as I wanted to compare which approach worked best for me and so that I couls safely revert if needed, I will clean up my code though as I have a feeling this may be a big problem if I don't do it now. I appreciate you explaining what you meant about good practice too as it will help in the future and it's a massive help to read more than just "clean up you code" as you could of written that and be done but you have taken time to explain why etc and explanations of anything be they code or just good practice always helps and it really does help me learn for next time.

1      

Really? Why on earth did I not think of that, so basically the decode approach decodes a "JSON" object from my new entry to then append it as such to the existing Array containing JSON, I guess this will need to be done every time?

Something else I would suggest is to stop thinking of your data as JSON. Once you decode JSON to a struct or class or whatnot, it's no longer JSON but rather a Swift data structure. You aren't appending a new JSON object to an array; you are appending a new Swift data structure to an array that can later be encoded to JSON for storage on your device.

JSON is how you store (encode) data that can be retrieved (decoded) at a later date. But it's not what you are working with in your Swift code.

1      

Really? Why on earth did I not think of that, so basically the decode approach decodes a "JSON" object from my new entry to then append it as such to the existing Array containing JSON, I guess this will need to be done every time?

Something else I would suggest is to stop thinking of your data as JSON. Once you decode JSON to a struct or class or whatnot, it's no longer JSON but rather a Swift data structure. You aren't appending a new JSON object to an array; you are appending a new Swift data structure to an array that can later be encoded to JSON for storage on your device.

JSON is how you store (encode) data that can be retrieved (decoded) at a later date. But it's not what you are working with in your Swift code.

I couldn't agree more @roosterboy, thinking of my data as an Array is what actually let me down the road of thinking calling append on its own would be sufficient, a post by @Obelix on a pervious question of mine helped me me to get out using the term JSON this JSON that as like you've both said once its been imported into my app it's just data.

What confused me the most was the initial error of not complying to my struct types and when your solution appeared that called a JSON decoder it made more sense. Would I be right in thinking that I could create a new temporary array to store data needed during the app run that can be appended to quickly and easily to then call your code at the end to decode it into the structs and then ultimately encode it back to JSON, thus saving to use that call all the time?

1      

Would I be right in thinking that I could create a new temporary array to store data needed during the app run that can be appended to quickly and easily to then call your code at the end to decode it into the structs and then ultimately encode it back to JSON, thus saving to use that call all the time?

I think one or both of us is confused. The usual process would be this:

  1. Read your app's data in from JSON stored on the device or in the cloud
  2. Decode the JSON into Swift data structures (i.e., structs and classes) using JSONDecoder and Codable conformance
  3. Manipulate your app's data as the user does whatever it is they do
  4. Encode your app's data out to JSON to store on the device or in the cloud using JSONEncoder and Codable conformance

After step 2, JSON is completely out of the picture until step 4. You use JSON for data storage, not for working with in your app.

1      

I completly understand what you mean @roosterboy but I don't think either of us are confused we just have a differant way of explaining it because what you say makes absolute sense to me and how I am thinking, my problem came when appending the data i wanted to into my existing array in the format that it would like me too which you kindly helped me solve.

1      

@Splinta wrote:

it's a massive help to read more than just "clean up you code" as you could of written that and be done but you have taken time to explain why etc and explanations of anything be they code or just good practice always helps and it really does help me learn for next time.

This is why I contribute to HackingWithSwift forums.

It's a reflection of Paul Hudson's core values of helping the community, new programmers in particular. He doesn't want you to just follow convention, or "JUST DO IT THIS WAY" follow the examples approach. He takes the time and is patient to explain the whys, along with the hows.

I particularly enjoy how he sets up a bit of code and gets it working. Then, a few lessons later reveals a much better way to perform the code you spent a few hours struggling to comprehend. Why didn't he start off with the better code? Well, it's simple. Your head would have exploded! You don't start in the kitchen preparing a six course meal. You start by learning how to boil an egg. Plus you learn about different pieces of equipment. You have to be comfortable with many, many cooking concepts before attempting a six course meal.

And I understand the frustration in many of these posts! I, too, look at code and wonder why it's not just working. So I try to explain it to myself as if I'm podcasting to a group. I try to link difficult SwiftUI concepts to familiar scenarios (kitchens and menus!) to help make connections. Some of my forum responses tend to be verbose. I get it. And also, not always 100% technically accurate.

So when I suggest NOT calling data JSON, and suggest you comment your code, and @rooster suggests tweaking your struct's names, and @delewareMathguy questions your decoding process, and @nigel links to an external article, think of these comments as a push in the direction to help you become more adept, more familiar, and comfortable. These help you progress.

1      

it's a massive help to read more than just "clean up you code" as you could of written that and be done but you have taken time to explain why etc and explanations of anything be they code or just good practice always helps and it really does help me learn for next time.

This is why I contribute to HackingWithSwift forums.

It's a reflection of Paul Hudson's core values of helping the community, new programmers in particular. He doesn't want you to just follow convention, or "JUST DO IT THIS WAY" follow the examples approach. He takes the time and is patient to explain the whys, along with the hows.

I particularly enjoy how he sets up a bit of code and gets it working. Then, a few lessons later reveals a much better way to perform the code you spent a few hours struggling to comprehend. Why didn't he start off with the better code? Well, it's simple. Your head would have exploded! You don't start in the kitchen preparing a six course meal. You start by learning how to boil an egg. Plus you learn about different pieces of equipment. You have to be comfortable with many, many cooking concepts before attempting a six course meal.

And I understand the frustration in many of these posts! I, too, look at code and wonder why it's not just working. So I try to explain it to myself as if I'm podcasting to a group. I try to use to link difficult SwiftUI concepts to familiar scenarios (kitchens and menus!) to help make connections. Some of my forum responses tend to be verbose. I get it. And also, not always 100% technically accurate.

So when I suggest NOT calling data JSON, and suggest you comment your code, and @rooster suggests tweaking your struct's names, and @delewareMathguy questions your decoding process, and @nigel links to an external article, think of these comments as a push in the direction to help you become more adept, more familiar, and comfortable. These help you progress.

@Obelix I genuinely couldn’t agree more with everything you have said including your suggestions both in the past and present too, I have owned and used a computer since 1995 when the internet was an infant and I have coded in the past, Delphi to be accurate but never understood or could find decent tutorials or helpful forums like this for good old C++ (now C# I believe) which I always wanted to learn but didn't and so my skills fell off a cliff but after watching Pauls tutorials on YouTube and having an idea in my head I joined Hacking With Swift with zero idea what I was doing, I took the 100 days and can now actually get a functional app but now I'm trying all sorts of different methods and alike to try and bolster my skills hence why I am concentrating on Arrays etc as I have never understood them much until now.

People like yourself, roosterboy and even Paul himself astound me at times with your knowledge of Swift, I sit there following along thinking how on earth do you remember all these specialist functions and syntax etc (as let’s face it Xcode is good but it’s not exactly helpful with its errors) and that’s why I much prefer explanations or nudges in the right direction as opposed to code because if I know where to look and why I am sure I will figure out how too, being given sample code is amazing and I thank you both for that too but without explanation of how and why to do things this way it’s just like cheating on a test, you get the grade but no idea how.

Anyway I should be careful as I might be taking this topic off topic but please know this, you guys and girls here are amazing you truly are and it’s a pleasure to be part of this community and I appreciate your patience and time too!

2      

You need to decode newData into a peopleData2.messages struct and then you can append it to people2.people[0].messages.


let people = People2()
print(people.people)

let newData = """
    {
        "number": 8,
        "sender": "Helen",
        "content": "Test"
    }
"""

if let newMessage = try? JSONDecoder().decode(peopleData2.messages.self, from: newData.data(using: .utf8)!) {
  people.people[0].messages.append(newMessage)
  print(people.people)
}

Hi Both,

Just wondering if I can pick your brains again please? After working out how the above code works I have been trying to append a completly new user to the peopleData2 struct (now renamed UserData) however even though convention would sound like your approach above would work it doesn't and I am unsure as to why. Here is how I am trying to approach this:

struct UserData: Codable {

    // changed all properties to var's from let's incase this was the issue. This struct was previously peopleData2

    var people: [person]

    struct person: Codable, Identifiable {
        var id: String
        var name: String
        var age: String
        var phone_number: String
        var messages: [messages]
    }

    struct messages: Codable, Identifiable {
        var id: Int { number }
        var number: Int
        var sender: String
        var content: String
    }
}

func appendPerson() {
    let newPer = """
        {
            "id": "321D1CA0-4EF9-4A03-A9F1-3BA20CD6E318",
            "name": "Joe",
            "age": "25",
            "phone_number": "01002331167",
            "messages": [
                    {
                        "number": 1,
                        "sender": "Joe",
                        "content": "This is a test person"
                    }
                ]
        }
    """

    print(user.people.count)

    if let newPerson = try? JSONDecoder().decode(UserData.person.self, from: newPer.data(using: .utf8)!) { // was peopleData2 before renaming to make more sense

        print(newPerson) // Never prints as I never reach this pont so I assume decoding is failing tried finding errors using a do catch block but falls past catch

        user.people.insert(newPerson, at: 2) // Tried this but knew wouldn't work

        user.people.append(newPerson) // a standad append as I assumed that as I was passing the data into the array in exactly as in the struct it would be a straight append.
    }
}

I know i'm missing something silly but i'm not sure what as I can't read any errors as non seem to generate as mentioned above. I understand that UserDate.people is an array of person and I have tried refrencing UserData.people itself but its obviously not available in this scenario.

Again I know i'm missing something but without errors I'm just left guessing and I have tried all I can think of including a [String:Any] dictionary array too.

Thanks for you're help (again) I do appreciate it!

1      

When decoding JSON, don't do this:

let newPerson = try? JSONDecoder().decode(UserData.person.self, from: newPer.data(using: .utf8)!)

That try? converts any error that occurs into an Optional and sets the value to nil, hiding the error from you entirely.

Do this instead:

do {
    let newPerson = try JSONDecoder().decode(UserData.person.self, from: newPer.data(using: .utf8)!)
    user.people.append(newPerson)
} catch {
    print(error)
}

1      

When decoding JSON, don't do this:

let newPerson = try? JSONDecoder().decode(UserData.person.self, from: newPer.data(using: .utf8)!)

That try? converts any error that occurs into an Optional and sets the value to nil, hiding the error from you entirely.

Do this instead:

do {
    let newPerson = try JSONDecoder().decode(UserData.person.self, from: newPer.data(using: .utf8)!)
    user.people.append(newPerson)
} catch {
    print(error)
}

I tried that earlier roosterboy and it didn't yeild any errors, just put it back in and still no errors then noticed that my internal people.count wasn't showing... then the lightbulb moment... it's still in a function wrapper and that function isn't called... how stupid do I feel now... thats there were no errors, it wasn't executing!

Sorry for wasting your time roosterboy but thanls again for your help, if anything it lit the bulb!

1      

Hacking with Swift is sponsored by Emerge

SPONSORED Optimize your app’s startup time, binary size, and overall performance using Emerge’s advanced app optimization and monitoring tools. Reliably measure app size, speed up your app's startup time with Emerge's Launch Booster, and much more. Emerge is actively used by many of the top mobile development teams in the world.

Find out more

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

Reply to this topic…

You need to create an account or log in to reply.

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.