Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating a saved ParseObject using saveAll produces error #405

Closed
4 tasks done
vdkdamian opened this issue Sep 6, 2022 · 13 comments · Fixed by #423
Closed
4 tasks done

Updating a saved ParseObject using saveAll produces error #405

vdkdamian opened this issue Sep 6, 2022 · 13 comments · Fixed by #423
Labels
type:bug Impaired feature or lacking behavior that is likely assumed

Comments

@vdkdamian
Copy link
Contributor

vdkdamian commented Sep 6, 2022

New Issue Checklist

Issue Description

I am unable to upload objects using the saveAll() method. I get the error: "ParseError code=105 error=Invalid field name: __type."

This is my struct:

struct PGRListItem: NVMParseObject {
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    var originalData: Data?
    
    var id: String { nvmId ?? Novem.generate_nvmId() }
    
    var userId: String?
    var nvmId: String?
    var timeStamp: Date?
    
    var deviceId: String?
    var deviceName: String?
    
    var selected: Bool?
    var shops: [Shop]?
    var brand: String?
    var priority: Int?
    var amount: Float?
    var price: [String : String]?
    
    var checkedHistory: [Date]?
    var uncheckedHistory: [Date]?
    var checkedTimes: Int?
    var uncheckedTimes: Int?
    var lastUsed: Date?
    
    init() {
        self.nvmId = Novem.generate_nvmId()
    }
}

Here is my code that saves the objects.

if let synchronizableObject = synchronizableObjects.first, synchronizableObjects.count == 1 {
    let object = try await synchronizableObject.save()
    
    self.removeSynchronizingObject(object.nvmId)
    try self.storeParseItems([object],
                             storeGroup: self.storeGroup,
                             encryption: self.encrypted ?? false,
                             fetch: .none)
    
} else {
    let results = try await synchronizableObjects.saveAll()
    try self.storeResults(results,
                          storeGroup: self.storeGroup,
                          encryption: self.encrypted ?? false)
}

Also might be important, if only one object needs to be saved, my code makes use of .save(). Multiple objects use .saveAll(). It's good to know that the .save() function never throws this error.

Steps to reproduce

Using the saveAll() method

Actual Outcome

ParseError code=105 error=Invalid field name: __type.

Expected Outcome

No error

Environment

Client

  • Parse Swift SDK version: 4.10.0
  • Xcode version: 14.0 beta 6
  • Operating system (iOS, macOS, watchOS, etc.): iOS, macOS
  • Operating system version: iOS 16, macOS 13

Server

  • Parse Server version: 4.5.0
  • Operating system: Back4app
  • Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): Back4app

Database

  • System (MongoDB or Postgres): MongoDB
  • Database version: 3.6
  • Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): Back4app

Logs

@parse-github-assistant
Copy link

parse-github-assistant bot commented Sep 6, 2022

Thanks for opening this issue!

  • 🚀 You can help us to fix this issue faster by opening a pull request with a failing test. See our Contribution Guide for how to make a pull request, or read our New Contributor's Guide if this is your first time contributing.

@mtrezza mtrezza added the type:bug Impaired feature or lacking behavior that is likely assumed label Sep 6, 2022
@cbaker6
Copy link
Contributor

cbaker6 commented Sep 6, 2022

I am unable to upload objects using the saveAll() method. I get the error: "ParseError code=105 error=Invalid field name: __type."

I'm not able to replicate your issue. Some notes:

  1. You are running on Xcode 14 beta, this isn't officially supported even though I've run the full test-suite on Xcode 14 Beta 5 and 6 and it passes without issues
  2. You never posted the code you are using that "actually" produces the saveAll error. Please update your issue with the code
  3. Please post the complete protocol of NVMParseObject. If you've customized a ParseObject, it's possible your customization is creating the issue
  4. Is Shop a ParseObject? If so, please post the code for it
  5. Is there a reason for var id: String { nvmId ?? Novem.generate_nvmId() }? Not sure if that's causing your current issue, but you are bound to run into issues with items that require Identifiable based on the code you've shown so far. A ParseObject comes with a default implementation of id, 90% of use-cases should use the default implementation shown here:
    /**
    A computed property that is the same value as `objectId` and makes it easy to use `ParseObject`'s
    as models in MVVM and SwiftUI.
    - note: `id` allows `ParseObjects`'s to be used even if they are unsaved and do not have an `objectId`.
    */
    var id: String {
    guard let objectId = self.objectId else {
    return UUID().uuidString
    }
    return objectId
    }
  6. I ran all of the code in Playgrounds that uses saveAll in Xcode 13.4.1 and Xcode 14 Beta 6 and all of it works as expected:
    //: Saving multiple GameScores at once.
    [score, score2].saveAll { results in
    switch results {
    case .success(let otherResults):
    var index = 0
    otherResults.forEach { otherResult in
    switch otherResult {
    case .success(let savedScore):
    print("""
    Saved \"\(savedScore.className)\" with
    points \(String(describing: savedScore.points)) successfully
    """)
    if index == 1 {
    score2ForFetchedLater = savedScore
    }
    index += 1
    case .failure(let error):
    assertionFailure("Error saving: \(error)")
    }
    }
    case .failure(let error):
    assertionFailure("Error saving: \(error)")
    }
    }
    //: Saving multiple GameScores at once using a transaction.
    //: May not work on MongoDB depending on your configuration.
    /*[score, score2].saveAll(transaction: true) { results in
    switch results {
    case .success(let otherResults):
    var index = 0
    otherResults.forEach { otherResult in
    switch otherResult {
    case .success(let savedScore):
    print("Saved \"\(savedScore.className)\" with points \(savedScore.points) successfully")
    if index == 1 {
    score2ForFetchedLater = savedScore
    }
    index += 1
    case .failure(let error):
    assertionFailure("Error saving: \(error)")
    }
    }
    case .failure(let error):
    assertionFailure("Error saving: \(error)")
    }
    }*/
    //: Save synchronously (not preferred - all operations on current queue).
    let savedScore: GameScore?
    do {
    savedScore = try score.save()
    } catch {
    savedScore = nil
    fatalError("Error saving: \(error)")
    }
    assert(savedScore != nil)
    assert(savedScore?.objectId != nil)
    assert(savedScore?.createdAt != nil)
    assert(savedScore?.updatedAt != nil)
    assert(savedScore?.points == 10)
    /*: To modify, need to make it a var as the value type
    was initialized as immutable. Using `mergeable`
    allows you to only send the updated keys to the
    parse server as opposed to the whole object.
    */
    guard var changedScore = savedScore?.mergeable else {
    fatalError("Should have produced mutable changedScore")
    }
    changedScore.points = 200
    let savedChangedScore: GameScore?
    do {
    savedChangedScore = try changedScore.save()
    print("Updated score: \(String(describing: savedChangedScore))")
    } catch {
    savedChangedScore = nil
    fatalError("Error saving: \(error)")
    }
    assert(savedChangedScore != nil)
    assert(savedChangedScore!.points == 200)
    assert(savedScore!.objectId == savedChangedScore!.objectId)
    let otherResults: [(Result<GameScore, ParseError>)]?
    do {
    otherResults = try [score, score2].saveAll()
    } catch {
    otherResults = nil
    fatalError("Error saving: \(error)")
    }
    assert(otherResults != nil)
    otherResults!.forEach { result in
    switch result {
    case .success(let savedScore):
    print("Saved \"\(savedScore.className)\" with points \(String(describing: savedScore.points)) successfully")
    case .failure(let error):
    assertionFailure("Error saving: \(error)")
    }
    }
    //: Batching saves with saved and unsaved pointer items.
    var author3 = Author(name: "Logan", book: newBook)
    let otherBook3 = Book(title: "I like this book")
    let otherBook4 = Book(title: "I like this book also")
    author3.otherBooks = [otherBook3, otherBook4]
    [author3].saveAll { result in
    switch result {
    case .success(let savedAuthorsAndBook):
    savedAuthorsAndBook.forEach { eachResult in
    switch eachResult {
    case .success(let savedAuthorAndBook):
    assert(savedAuthorAndBook.objectId != nil)
    assert(savedAuthorAndBook.createdAt != nil)
    assert(savedAuthorAndBook.updatedAt != nil)
    assert(savedAuthorAndBook.otherBooks?.count == 2)
    /*:
    Notice the pointer objects have not been updated on the
    client.If you want the latest pointer objects, fetch and include them.
    */
    print("Saved \(savedAuthorAndBook)")
    case .failure(let error):
    assertionFailure("Error saving: \(error)")
    }
    }
    case .failure(let error):
    assertionFailure("Error saving: \(error)")
    }
    }
    //: Batching saves with unsaved pointer items.
    var newBook2 = Book(title: "world")
    var author4 = Author(name: "Scott", book: newBook2)
    author4.otherBooks = [otherBook3, otherBook4]
    [author4].saveAll { result in
    switch result {
    case .success(let savedAuthorsAndBook):
    savedAuthorsAndBook.forEach { eachResult in
    switch eachResult {
    case .success(let savedAuthorAndBook):
    assert(savedAuthorAndBook.objectId != nil)
    assert(savedAuthorAndBook.createdAt != nil)
    assert(savedAuthorAndBook.updatedAt != nil)
    assert(savedAuthorAndBook.otherBooks?.count == 2)
    /*:
    Notice the pointer objects have not been updated on the
    client.If you want the latest pointer objects, fetch and include them.
    */
    print("Saved \(savedAuthorAndBook)")
    case .failure(let error):
    assertionFailure("Error saving: \(error)")
    }
    }
    case .failure(let error):
    assertionFailure("Error saving: \(error)")
    }
    }
    //: Using this relation, you can create one-to-many relationships with other `ParseObjecs`,
    //: similar to `users` and `roles`.
    //: All `ParseObject`s have a `ParseRelation` attribute that be used on instances.
    //: For example, the User has:
    var relation = User.current!.relation
    let score1 = GameScore(points: 53)
    let score2 = GameScore(points: 57)
    //: Add new child relationships.
    [score1, score2].saveAll { result in
    switch result {
    case .success(let savedScores):
    //: Make an array of all scores that were properly saved.
    let scores = savedScores.compactMap { try? $0.get() }
    do {
    guard let newRelations = try relation?.add("scores", objects: scores) else {
    print("Error: should have unwrapped relation")
    return
    }
    newRelations.save { result in
    switch result {
    case .success(let saved):
    print("The relation saved successfully: \(saved)")
    print("Check \"points\" field in your \"_User\" class in Parse Dashboard.")
    case .failure(let error):
    print("Error saving role: \(error)")
    }
    }
    } catch {
    print(error)
    }
    case .failure(let error):
    print("Could not save scores. \(error)")
    }
    }

I recommend:

  1. Forking the repo and running all of the saveAll code in Playgrounds (step 6 above) connected to your server or development server on whatever service you use to host. If this code works as expected, proceed to step 2
  2. Modify the Playgrounds to a situation that represents yours. Are you able to replicate the error? If so, post all of the code
  3. Try the modified code in the previous step in Xcode 13.4.1, does it still throw an issue?

@cbaker6
Copy link
Contributor

cbaker6 commented Sep 7, 2022

In addition, I suspect:

init() {
        self.nvmId = Novem.generate_nvmId()
}

is related to your issue based on "ParseError code=105 error=Invalid field name: __type." The empty initialize should be "empty". If you want to create another initializer, place it in an extension and it should take at least one value. This is mentioned in the Documentation, in previous comments #315 (comment):

From a POP standpoint, if the ParseObject protocol doesn't require init(), developers can make non-optional values and it's impossible to initialize new versions of their ParseObject's internally in the SDK; preventing .mutable, merge, or anything else that needs a new copy inside the app. For value types with all optional properties, the compiler provides the init() automatically; assuming all other inits are defined in an extension. This is essential since this SDK doesn't use reference types and at times need to return a fresh copy of the value type to the developer.

and Playgrounds:

//: It's recommended to place custom initializers in an extension
//: to preserve the memberwise initializer.
extension GameScore {
init(points: Int) {
self.points = points
}
init(objectId: String?) {
self.objectId = objectId
}
}

Your ParseObject should be:

struct PGRListItem: NVMParseObject {
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    var originalData: Data?
    
    /* 
       I highly doubt you need this here and you probably have to manage more 
       code in the long run because you have it here. If you really want it, it should look
       more like the following. Your nvmId will only get used when there's no objectId (basically
       when the ParseObject hasn't been saved to the server).
   */
    // var id: String { objectId ?? nvmId ?? Novem.generate_nvmId() }
    
    var userId: String?
    var nvmId: String?
    var timeStamp: Date?
    
    var deviceId: String?
    var deviceName: String?
    
    var selected: Bool?
    var shops: [Shop]?
    var brand: String?
    var priority: Int?
    var amount: Float?
    var price: [String : String]?
    
    var checkedHistory: [Date]?
    var uncheckedHistory: [Date]?
    var checkedTimes: Int?
    var uncheckedTimes: Int?
    var lastUsed: Date?
}

/*
  It is recommended to place custom initializers in an extension 
  to preserve the memberwise initializer. 
*/
extension PGRListItem {
  init(nvmId: String?) {
        self.nvmId = nvmId ?? Novem.generate_nvmId()
  }
}

@vdkdamian
Copy link
Contributor Author

I totally forgot I can't use custom init().

But my custom ID is for the exact reason as you mention. I had an issue where I needed the ID on objects that where not saved yet.

I'll try to reform all the mess I made later today.

@vdkdamian
Copy link
Contributor Author

I completely removed the custom init, and the custom id var. It still shows the error.

@vdkdamian
Copy link
Contributor Author

  1. You never posted the code you are using that "actually" produces the saveAll error. Please update your issue with the code

I'll do.

  1. Please post the complete protocol of NVMParseObject. If you've customized a ParseObject, it's possible your customization is creating the issue
// MARK: - NVMParseObject
public protocol NVMParseObject: ParseObject, NVMUserable, NVMSynchronizable, Identifiable {
    
    /**
     An ID for use by Novem instead of objectId. This id should be the same as nvmId.
     
     - warning: Changing this would be a breaking change and can result in various issues.
     */
    var id: String { get }
}

extension NVMParseObject {
    
    /**
     An ID for use by Novem instead of objectId. If no nvmId is found, it will generate a new one.
     */
     func nvmId() -> String { self.nvmId ?? Novem.generate_nvmId() }
}

// MARK: - NVMSynchronizable

/**
 Use this protocol to allow the full synchronization of Parse Object to the Novem server.
 */
public protocol NVMSynchronizable {
    
    /**
     A  `String` used to synchronize between local en cloud.
     
     - warning: This property is not intended to be set or modified by the developer.
     
     - note: **This value may not be nil.** It's optional because `ParseSwift` requires there variables to be optional, but all use by the Novem framework will force-unwrap this when used. If this value is nil, the object is not correctly set and shouldn't exist.
    */
    var nvmId: String? { get }
    
    /**
     A  `Date` used to synchronize between local en cloud.
     
     - warning: This property is not intended to be set or modified by the developer.
     
     - note: **This value may not be nil.** It's optional because `ParseSwift` requires there variables to be optional, but all use by the Novem framework will force-unwrap this when used. If this value is nil, the object is not correctly set and shouldn't exist.
    */
    var timeStamp: Date? { get set }
}

// MARK: - NVMUserable
public protocol NVMUserable: Codable {
    
    /**
    The Novem userId for this object.
    */
    var userId: String? { get set }
}
  1. Is Shop a ParseObject? If so, please post the code for it

It's not a ParseObject. It's just a struct conforming to Codable

@vdkdamian
Copy link
Contributor Author

I recommend:

  1. Forking the repo and running all of the saveAll code in Playgrounds (step 6 above) connected to your server or development server on whatever service you use to host. If this code works as expected, proceed to step 2

I don't know how to connect the Playgrounds to my server. Is there a tutorial or something?

@cbaker6
Copy link
Contributor

cbaker6 commented Sep 9, 2022

I don't know how to connect the Playgrounds to my server. Is there a tutorial or something?

This is discussed In CONTRIBUTING.md and towards the top of README.md.

@gemiren
Copy link

gemiren commented Sep 30, 2022

I'm having the exact issue. saveAll() works for new objects (object never saved to Parse server yet) but doesn't work for modifying existing objects (object already exists on the Parse Sever).

Below is the log from Parse Sever when saveAll() is used to save existing objects. You can see that the body contents wrong encoding of the object. No changed keys of the object are sent to the server.

verbose: REQUEST for [POST] /parse//batch: {
  "requests": [
    {
      "body": {
        "__type": "Pointer",
        "className": "TestingParseObject",
        "objectId": "38vPLCcGKA"
      },
      "method": "PUT",
      "path": "/parse/classes/TestingParseObject/38vPLCcGKA"
    }
  ],
  "transaction": false
} {"body":{"requests":[{"body":{"__type":"Pointer","className":"TestingParseObject","objectId":"38vPLCcGKA"},"method":"PUT","path":"/parse/classes/TestingParseObject/38vPLCcGKA"}],"transaction":false},"headers":{"accept":"*/*","accept-encoding":"gzip, deflate","accept-language":"en-US,en;q=0.9","connection":"keep-alive","content-length":"204","content-type":"application/json","host":"147.182.187.161:1337","user-agent":"PodcastApp/1 CFNetwork/1390 Darwin/22.0.0","x-parse-application-id":"LUqHFAQFXnmfp6TN","x-parse-client-version":"swift4.14.1","x-parse-installation-id":"cecc6f78-72f6-49c0-8b44-2e7ce66b2b0e","x-parse-request-id":"d121c68b-49fc-488f-969b-ef75e2fa26b4","x-parse-session-token":"r:8b0b27669791b030b7d88ff289eab7a6"},"method":"POST","url":"/parse//batch"}
verbose: RESPONSE from [POST] /parse//batch: {
  "response": [
    {
      "error": {
        "code": 105,
        "error": "Invalid field name: __type."
      }
    }
  ]
} {"result":{"response":[{"error":{"code":105,"error":"Invalid field name: __type."}}]}}

When saving new objects with saveAll() the log from Parse Server looks like this:

verbose: REQUEST for [POST] /parse//batch: {
  "requests": [
    {
      "body": {
        "ACL": {
          "peYQ4RYdyp": {
            "read": true,
            "write": true
          }
        },
        "favorite": true,
        "hide": 0,
        "playedTime": 0,
      },
      "method": "POST",
      "path": "/parse/classes/TestingParseObject"
    }
  ],
  "transaction": false
}
verbose: RESPONSE from [POST] /parse//batch: {
  "response": [
    {
      "success": {
        "objectId": "38vPLCcGKA",
        "createdAt": "2022-09-30T00:50:20.327Z"
      }
    }
  ]
} {"result":{"response":[{"success":{"createdAt":"2022-09-30T00:50:20.327Z","objectId":"38vPLCcGKA"}}]}}

The body content is the object itself.

Digging into the source code, it looks the culprit is from this line

if let pointer = try? PointerType(object) {
. For existing objects the pointer encoding is send to the Parse Sever.

For new objects, it goes to the line

, which encodes the object itself correctly.

@cbaker6 cbaker6 linked a pull request Oct 9, 2022 that will close this issue
5 tasks
@cbaker6 cbaker6 changed the title "ParseError code=105 error=Invalid field name: __type." Updating a saved ParseObject using save produces error Oct 9, 2022
@vdkdamian
Copy link
Contributor Author

Fix is working! Thanks

@cbaker6
Copy link
Contributor

cbaker6 commented Oct 9, 2022

@bcbeta can you provide the code for your objects and your call to save? It's best to edit the Playgrounds examples and make it produce the error as mentioned here: #405 (comment). From the example you described:

when I create 2 related parse objects and save one of them

The relevant playgrounds code seems to work in my testing of the latest version:

var newBook = Book(title: "hello")
let author = Author(name: "Alice", book: newBook)
author.save { result in
switch result {
case .success(let savedAuthorAndBook):
assert(savedAuthorAndBook.objectId != nil)
assert(savedAuthorAndBook.createdAt != nil)
assert(savedAuthorAndBook.updatedAt != nil)
print("Saved: \(savedAuthorAndBook)")
case .failure(let error):
assertionFailure("Error saving: \(error)")
}
}
//: Pointer array.
let otherBook1 = Book(title: "I like this book")
let otherBook2 = Book(title: "I like this book also")
var author2 = Author(name: "Bruce", book: newBook)
author2.otherBooks = [otherBook1, otherBook2]
author2.save { result in
switch result {
case .success(let savedAuthorAndBook):
assert(savedAuthorAndBook.objectId != nil)
assert(savedAuthorAndBook.createdAt != nil)
assert(savedAuthorAndBook.updatedAt != nil)
assert(savedAuthorAndBook.otherBooks?.count == 2)
/*:
Notice the pointer objects have not been updated on the
client.If you want the latest pointer objects, fetch and include them.
*/
print("Saved: \(savedAuthorAndBook)")
case .failure(let error):
assertionFailure("Error saving: \(error)")
}
}

Also, if you are using save() you have a different issue from this one. You should open a separate issue and link to not having issues until version 4.14.2

@cbaker6 cbaker6 changed the title Updating a saved ParseObject using save produces error Updating a saved ParseObject using saveAll produces error Oct 9, 2022
@bcbeta
Copy link

bcbeta commented Oct 9, 2022

Yes sorry I deleted my comment because I need to spend a little more time describing the issue. I’ll try to get to it tonight. Something with the latest update broke my apps ability to save objects.

@cbaker6
Copy link
Contributor

cbaker6 commented Oct 9, 2022

Something with the latest update broke my apps ability to save objects.

This is unfortunate. I ran the whole Playgrounds suite along with the CI test suite and didn't see any breaks. Feel free to open a new issue if you discover how to replicate the problem you are facing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:bug Impaired feature or lacking behavior that is likely assumed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants