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

ParseInstallation does not store object ID in key-value store #115

Closed
jt9897253 opened this issue Apr 3, 2021 · 31 comments · Fixed by #116
Closed

ParseInstallation does not store object ID in key-value store #115

jt9897253 opened this issue Apr 3, 2021 · 31 comments · Fixed by #116
Labels
type:bug Impaired feature or lacking behavior that is likely assumed

Comments

@jt9897253
Copy link
Contributor

Assume a struct MyParseInstallation conforming to ParseInstallation.

If I understand the code correctly, calling MyParseInstallation.current?.save { (result: Result<AppInstallInfo, ParseError>) in [...] } intends to persist the ParseInstallation data to the server.

However, calling MyParseInstallation.current?.fetch(completion: { (result: Result<AppInstallInfo, ParseError>) in [...]} always returns a failure.

Problem seems to be that objectId of MyParseInstallation is not persisted to the ParseKeyValueStore. Thus, the fetch returns ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Cannot fetch an object without id")

Another effect of not storing objectId in the key-value store is that multiple calls to MyParseInstallation.current?.save result in multiple ParseInstallation objects being created on the server, all with the same data but different ids.

Tested on both the latest release and the removeDispatch branch.

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

I'm assuming you are using ParseSwift on Linux/Android? This is very possible, currently looking into the problem...

@cbaker6 cbaker6 added the type:bug Impaired feature or lacking behavior that is likely assumed label Apr 3, 2021
@jt9897253
Copy link
Contributor Author

I'm assuming you are using ParseSwift on Linux/Android?

No, tested on iOS and macCatalyst this time. I'll check if #116 addresses the issue

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

I'm assuming you are using ParseSwift on Linux/Android?

No, tested on iOS and macCatalyst this time. I'll check if #116 addresses the issue

Makes sense. When I found what the issue was it showed up across all OSs.

@funkenstrahlen
Copy link

I also see this issue. Happy to see a fix is already in the works.

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

I also see this issue. Happy to see a fix is already in the works.

Sure, as soon as one of you confirms it works I will merge

@jt9897253
Copy link
Contributor Author

I'm not sure if the behavior improved. Tested the following (branch "current"): save the Installation on app start
Behavior is:

  • On first app start, the installation is stored on the server correctly
  • On subsequent app starts, getting the The data couldn’t be read because it is missing. from Parse Installation #93

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

I'm not sure if the behavior improved. Tested the following (branch "current"): save the Installation on app start
Behavior is:

  • On first app start, the installation is stored on the server correctly
  • On subsequent app starts, getting the The data couldn’t be read because it is missing. from Parse Installation #93

@jt9897253 Can you post your actual error and your whole Installation struct. Also, can you post what you are calling when you receive this error?

@jt9897253
Copy link
Contributor Author

Install struct:

private struct AppInstallInfo: ParseInstallation {
    // required for `ParseObject`
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    
    // required for `ParseInstallation`
    var installationId: String?
    var deviceType: String?
    var deviceToken: String?
    var badge: Int?
    var timeZone: String?
    var channels: [String]?
    var appName: String?
    var appIdentifier: String?
    var appVersion: String?
    var parseVersion: String?
    var localeIdentifier: String?
    // custom keys can be added here
}

This should be enough to reproduce the issue (parse server running locally in Docker):

if let serverURL = URL(string: "http://localhost:1337/parse") {
    ParseSwift.initialize(applicationId: "APPLICATION_ID", clientKey: "CLIENT_KEY", serverURL: serverURL, keyValueStore: myStore)
    
    AppInstallInfo.current?.save { (result: Result<AppInstallInfo, ParseError>) in
        switch result {
        case .success(let updatedAppInstallInfo):
            callback("successfully saved installation on server: \(updatedAppInstallInfo)")
        case .failure(let error):
            callback("failed to save installation on server: \(error)")
        }
    }
} else {
    callback("invalid parse server url")
}

Full error:

failed to save installation on server: ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Error decoding parse-server response: Optional(<NSHTTPURLResponse: 0x600002f606c0> { URL: http://localhost:1337/parse/installations } { Status Code: 200, Headers {\n    \"Access-Control-Allow-Headers\" =     (\n        \"X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control\"\n    );\n    \"Access-Control-Allow-Methods\" =     (\n        \"GET,PUT,POST,DELETE,OPTIONS\"\n    );\n    \"Access-Control-Allow-Origin\" =     (\n        \"*\"\n    );\n    \"Access-Control-Expose-Headers\" =     (\n        \"X-Parse-Job-Status-Id, X-Parse-Push-Status-Id\"\n    );\n    Connection =     (\n        \"keep-alive\"\n    );\n    \"Content-Length\" =     (\n        40\n    );\n    \"Content-Type\" =     (\n        \"application/json; charset=utf-8\"\n    );\n    Date =     (\n        \"Sat, 03 Apr 2021 16:24:52 GMT\"\n    );\n    Etag =     (\n        \"W/\\\"28-73f5/AChhbjMHdlSNQFN7RU2U30\\\"\"\n    );\n    \"Keep-Alive\" =     (\n        \"timeout=5\"\n    );\n    \"X-Powered-By\" =     (\n        Express\n    );\n} }) with error: The data couldn’t be read because it is missing. Format: Optional(\"{\\\"updatedAt\\\":\\\"2021-04-03T16:24:52.550Z\\\"}\")")

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

Are you still using the same device you were using when it wasn't working? If so, can you try logging out so a new installation is generated

If you want to keep your current installation, you can create a query to find your current installation on the server based on the installationId, then save the objectId to your current installation and then save back to the server.

Or, you can delete your installation in Parse Dashboard and run your app again

I've added an auto migration/deletion that will ask for a new installation if the objectId for the current installation isn't saved to the Keychain, so you shouldn't need to do anything else but update the SDK.

@jt9897253
Copy link
Contributor Author

jt9897253 commented Apr 3, 2021

This was tested on iOS simulator. I just deleted the app, everything from _Installation on the server and the issue is still present on second start.

If you want to keep your current installation, you can create a query to find your current installation on the server based on the installationId

I think I tried that, but was not allowed (got an error message like "you are not allow to query _Installation").

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

I think I see the issue, I'll let you know when I have an update

@jt9897253
Copy link
Contributor Author

If you want to keep your current installation, you can create a query to find your current installation on the server based on the installationId

Also, as said above, I tried fetching the current installation with

AppInstallInfo.current?.fetch(completion: { (result: Result<AppInstallInfo, ParseError>) in [...]}

but this did not work, presumably as the installation's object id is nil when fetching.

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

I just made a commit that should fix the issue

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

Be sure to delete your installations from your Parse Dashboard so they are saved properly. You shouldn't need to do anything else.

I've added an auto migration/deletion that will ask for a new installation if the objectId for the current installation isn't saved to the Keychain, so you shouldn't need to do anything else but update the SDK.

@jt9897253
Copy link
Contributor Author

jt9897253 commented Apr 3, 2021

Still not working here.

In the Installation playground file, running save twice does work.

I can reproduce this in the app: when saving the installation two times after one another, I do not get an error. If I then stop the app and run it again, I see the The data couldn’t be read because it is missing. error.

Is the in memory store working correctly but when persisting to disk using a custom key-value store (called myStore in the above example code) it goes wrong?

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

I can reproduce this in the app: when saving the installation two times after one another, I do not get an error. If I then stop the app and run it again, I see the The data couldn’t be read because it is missing. error.

Can you post how you are saving? It's easier to see what's going on when you post what you are doing and the "complete" errors you are getting.

Is the in memory store working correctly but when persisting to disk using a custom key-value store (called myStore in the above example code) it goes wrong?

I'm not sure what you mean. Are you asking me if your custom key value store is working correctly? You will need to post the code for it for me to determine that

@jt9897253
Copy link
Contributor Author

jt9897253 commented Apr 3, 2021

Can you post how you are saving? It's easier to see what's going on when you post what you are doing and the "complete" errors you are getting.

Using the following code:

if let serverURL = URL(string: "http://localhost:1337/parse") {
    ParseSwift.initialize(applicationId: "APPLICATION_ID", clientKey: "CLIENT_KEY", serverURL: serverURL, keyValueStore: myStore)
    
    AppInstallInfo.current?.save { (result: Result<AppInstallInfo, ParseError>) in
        switch result {
        case .success(_):
            print("successfully saved installation on server")
        case .failure(let error):
            print("failed to save installation on server: \(error)")
        }
    }
    
    AppInstallInfo.current?.save { (result: Result<AppInstallInfo, ParseError>) in
        switch result {
        case .success(_):
            print("successfully saved installation on server")
        case .failure(let error):
            print("failed to save installation on server: \(error)")
        }
    }
} else {
    print("invalid parse server url")
}

Results in the following output the first time the app is started:

successfully saved installation on server
successfully saved installation on server

Results in the following output the second time the app is started:

failed to save installation on server: ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Error decoding parse-server response: Optional(<NSHTTPURLResponse: 0x60000246c020> { URL: http://localhost:1337/parse/installations } { Status Code: 200, Headers {\n    \"Access-Control-Allow-Headers\" =     (\n        \"X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control\"\n    );\n    \"Access-Control-Allow-Methods\" =     (\n        \"GET,PUT,POST,DELETE,OPTIONS\"\n    );\n    \"Access-Control-Allow-Origin\" =     (\n        \"*\"\n    );\n    \"Access-Control-Expose-Headers\" =     (\n        \"X-Parse-Job-Status-Id, X-Parse-Push-Status-Id\"\n    );\n    Connection =     (\n        \"keep-alive\"\n    );\n    \"Content-Length\" =     (\n        40\n    );\n    \"Content-Type\" =     (\n        \"application/json; charset=utf-8\"\n    );\n    Date =     (\n        \"Sat, 03 Apr 2021 18:20:24 GMT\"\n    );\n    Etag =     (\n        \"W/\\\"28-jHcGQkRKlm9UOcnp+krhVPlfEmE\\\"\"\n    );\n    \"Keep-Alive\" =     (\n        \"timeout=5\"\n    );\n    \"X-Powered-By\" =     (\n        Express\n    );\n} }) with error: The data couldn’t be read because it is missing. Format: Optional(\"{\\\"updatedAt\\\":\\\"2021-04-03T18:20:24.574Z\\\"}\")")
failed to save installation on server: ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Error decoding parse-server response: Optional(<NSHTTPURLResponse: 0x600002459160> { URL: http://localhost:1337/parse/installations } { Status Code: 200, Headers {\n    \"Access-Control-Allow-Headers\" =     (\n        \"X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control\"\n    );\n    \"Access-Control-Allow-Methods\" =     (\n        \"GET,PUT,POST,DELETE,OPTIONS\"\n    );\n    \"Access-Control-Allow-Origin\" =     (\n        \"*\"\n    );\n    \"Access-Control-Expose-Headers\" =     (\n        \"X-Parse-Job-Status-Id, X-Parse-Push-Status-Id\"\n    );\n    Connection =     (\n        \"keep-alive\"\n    );\n    \"Content-Length\" =     (\n        40\n    );\n    \"Content-Type\" =     (\n        \"application/json; charset=utf-8\"\n    );\n    Date =     (\n        \"Sat, 03 Apr 2021 18:20:24 GMT\"\n    );\n    Etag =     (\n        \"W/\\\"28-QGBaxYJkLeOr9i2dnKIxCpKMna0\\\"\"\n    );\n    \"Keep-Alive\" =     (\n        \"timeout=5\"\n    );\n    \"X-Powered-By\" =     (\n        Express\n    );\n} }) with error: The data couldn’t be read because it is missing. Format: Optional(\"{\\\"updatedAt\\\":\\\"2021-04-03T18:20:24.576Z\\\"}\")")

myStore (of type MyStorage) is simply persisting to user defaults:

struct MyStorage: ParseKeyValueStore {
    static private let prefix = "MyStorage"
    static private let encoder = JSONEncoder()
    static private let decoder = JSONDecoder()
    
    mutating func delete(valueFor key: String) throws {
        UserDefaults.standard.removeObject(forKey: key)
        UserDefaults.standard.synchronize()
    }
    
    mutating func deleteAll() throws {
        for (key, _) in UserDefaults.standard.dictionaryRepresentation() {
            if key.prefix(Self.prefix.count) == Self.prefix {
                UserDefaults.standard.removeObject(forKey: key)
            }
        }
        UserDefaults.standard.synchronize()
    }
    
    mutating func get<T>(valueFor key: String) throws -> T? where T: Decodable {
        if let object = UserDefaults.standard.data(forKey: "\(Self.prefix)\(key)") as Data? {
            return try Self.decoder.decode(T.self, from: object)
        }
        
        return nil
    }
    
    mutating func set<T>(_ object: T, for key: String) throws where T: Encodable {
        let encodedObject = try Self.encoder.encode(object)
        UserDefaults.standard.setValue(encodedObject, forKey: "\(Self.prefix)\(key)")
        UserDefaults.standard.synchronize()
    }
}

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

Using the following code:

You shouldn't be saving like this, these are two asynchronous calls, there's a high probability they will clash. You need to either switch them to synchronous calls or use below (playgrounds does it because you are suppose to complete each line step-by-step):

if let serverURL = URL(string: "http://localhost:1337/parse") {
    ParseSwift.initialize(applicationId: "APPLICATION_ID", clientKey: "CLIENT_KEY", serverURL: serverURL, keyValueStore: myStore)
    
    AppInstallInfo.current?.save { (result: Result<AppInstallInfo, ParseError>) in
        switch result {
        case .success(_):
            print("successfully saved installation on server")
        AppInstallInfo.current?.save { (result: Result<AppInstallInfo, ParseError>) in
        switch result {
        case .success(_):
            print("successfully saved installation on server")
        case .failure(let error):
            print("failed to save installation on server: \(error)")
        }
    }
        case .failure(let error):
            print("failed to save installation on server: \(error)")
        }
    }
} else {
    print("invalid parse server url")
}

Can you report after you make the fix above. The other issues you mentioned are most likely dependent on how you were saving.

@jt9897253
Copy link
Contributor Author

When performing the asynchronous network calls sequentially the output is as follows.

First run of app:

successfully saved installation on server
successfully saved installation on server

Second run of app:

failed to save installation on server: ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Error decoding parse-server response: Optional(<NSHTTPURLResponse: 0x600002e68680> { URL: http://localhost:1337/parse/installations } { Status Code: 200, Headers {\n    \"Access-Control-Allow-Headers\" =     (\n        \"X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control\"\n    );\n    \"Access-Control-Allow-Methods\" =     (\n        \"GET,PUT,POST,DELETE,OPTIONS\"\n    );\n    \"Access-Control-Allow-Origin\" =     (\n        \"*\"\n    );\n    \"Access-Control-Expose-Headers\" =     (\n        \"X-Parse-Job-Status-Id, X-Parse-Push-Status-Id\"\n    );\n    Connection =     (\n        \"keep-alive\"\n    );\n    \"Content-Length\" =     (\n        40\n    );\n    \"Content-Type\" =     (\n        \"application/json; charset=utf-8\"\n    );\n    Date =     (\n        \"Sat, 03 Apr 2021 18:36:52 GMT\"\n    );\n    Etag =     (\n        \"W/\\\"28-IrAxbNLwehO8sf9OQ3bvDlp9cR8\\\"\"\n    );\n    \"Keep-Alive\" =     (\n        \"timeout=5\"\n    );\n    \"X-Powered-By\" =     (\n        Express\n    );\n} }) with error: The data couldn’t be read because it is missing. Format: Optional(\"{\\\"updatedAt\\\":\\\"2021-04-03T18:36:52.394Z\\\"}\")")

Error message is shown once as the second save is only performed on success of the first save.

Thus, I do not think this is related to concurrency.

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

What happens when you don't use your store. Initializing without it?

@jt9897253
Copy link
Contributor Author

When using

if let serverURL = URL(string: "http://localhost:1337/parse") {
    ParseSwift.initialize(applicationId: "APPLICATION_ID", clientKey: "CLIENT_KEY", serverURL: serverURL, keyValueStore: nil)
    
    AppInstallInfo.current?.save { (result: Result<AppInstallInfo, ParseError>) in
        switch result {
        case .success(_):
            print("successfully saved installation on server")
            AppInstallInfo.current?.save { (result: Result<AppInstallInfo, ParseError>) in
                switch result {
                case .success(_):
                    print("successfully saved installation on server")
                case .failure(let error):
                    print("failed to save installation on server: \(error)")
                }
            }
        case .failure(let error):
            print("failed to save installation on server: \(error)")
        }
    }
} else {
    print("invalid parse server url")
}

Output for first run of app:

successfully saved installation on server
successfully saved installation on server

Output for the second run of app:

failed to save installation on server: ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Error decoding parse-server response: Optional(<NSHTTPURLResponse: 0x6000017845a0> { URL: http://localhost:1337/parse/installations } { Status Code: 200, Headers {\n    \"Access-Control-Allow-Headers\" =     (\n        \"X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control\"\n    );\n    \"Access-Control-Allow-Methods\" =     (\n        \"GET,PUT,POST,DELETE,OPTIONS\"\n    );\n    \"Access-Control-Allow-Origin\" =     (\n        \"*\"\n    );\n    \"Access-Control-Expose-Headers\" =     (\n        \"X-Parse-Job-Status-Id, X-Parse-Push-Status-Id\"\n    );\n    Connection =     (\n        \"keep-alive\"\n    );\n    \"Content-Length\" =     (\n        40\n    );\n    \"Content-Type\" =     (\n        \"application/json; charset=utf-8\"\n    );\n    Date =     (\n        \"Sat, 03 Apr 2021 19:37:23 GMT\"\n    );\n    Etag =     (\n        \"W/\\\"28-hYHodbGPDfWZ3+FzixQzoY+6VGw\\\"\"\n    );\n    \"Keep-Alive\" =     (\n        \"timeout=5\"\n    );\n    \"X-Powered-By\" =     (\n        Express\n    );\n} }) with error: The data couldn’t be read because it is missing. Format: Optional(\"{\\\"updatedAt\\\":\\\"2021-04-03T19:37:23.861Z\\\"}\")")

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

I'm not able to replicate your problem. I've tried in my own app and in playgrounds:

//Async
Installation.current?.save {
      print($0)
      Installation.current?.save {
            print($0) 
      }
 }
//Sync
try? Installation.current?.save()
try? Installation.current?.save()

Both work fine for me along with playgrounds.

Where are you initializing the Parse SDK? I also mention #93 (comment) about not saving the installation right after initialization. You can see the next comment for the reasons for why it's not needed in most cases #93 (comment). If you really want to initialize there you can try a "dispatch after a second or 2" to see if it helps.

This thread will close once the PR is merged. If you want to try to create a test case in the test files that recreate your use-case and open back the issue, feel free

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 3, 2021

@funkenstrahlen can you let us know if this works for you?

@funkenstrahlen
Copy link

Sure, will try this in the next days and provide feedback.

@funkenstrahlen
Copy link

I use ParseSwift 1.3.0 now. I can successfully login / logout with authenticated users and anonymous users without seeing any issues. Saving ParseInstallation.current works as expected in all cases. My code does not contain any ParseInstallation.current?.fetch calls though.

@funkenstrahlen
Copy link

Thank you for your work and time @cbaker6 ❤️

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 4, 2021

I use ParseSwift 1.3.0 now. I can successfully login / logout with authenticated users and anonymous users without seeing any issues. Saving ParseInstallation.current works as expected in all cases. My code does not contain any ParseInstallation.current?.fetch calls though.

@funkenstrahlen thanks for your feedback! I made sure to test out Installation.fetch on a server in an attempt to cover corner cases and it seems to work fine now that the 1st objectId saves properly. Question, did you just upgrade to 1.3.0 and let the auto installation migration (it checks for a locally saved installation without an objectId, deletes it, and creates another one), or did you delete your Installations in the dashboard? I ask this to see the ease of upgrading. Besides removing “DispatchQueue” from around Installation was there anything else you had to do? If you can give a quick step-by-step, we can link to it when people have questions.

@funkenstrahlen
Copy link

Question, did you just upgrade to 1.3.0 and let the auto installation migration (it checks for a locally saved installation without an objectId, deletes it, and creates another one), or did you delete your Installations in the dashboard?

I am currently only testing in the simulator and completely erased the simulator storage after upgrading to 1.3.0.

Therefore all installations I tested are created in a fresh state and I can not comment if the upgrade process works as expected.

@funkenstrahlen
Copy link

Besides removing “DispatchQueue” from around Installation was there anything else you had to do?

I did not change anything else than removing the additional dispatch after logout before accessing installation.

As mentioned before I completely erased my existing state in the simulator for a fresh start after that.

@jt9897253
Copy link
Contributor Author

Just tested again with release 1.3.0 and I think ParseInstallation.current?.fetch does indeed work correctly now. I do not fully understand what went wrong the last time I checked but assume an error on my side.
@cbaker6 Thank you for all the time you are investing into this project!

@cbaker6
Copy link
Contributor

cbaker6 commented Apr 5, 2021

I’m glad it’s working for you!

One note about your custom key/value store, I suggest you use the Parse JSON encoder/decoder as Parse has a custom date strategy the stock encoder doesn’t account for. Besides the date strategy, it’s the stock encoder. This can end up causing unexpected behavior or even worse it won’t decode your objects from the store (you can use any of your ParseObjects to get encoder or decoder) https://github.com/netreconlab/ParseCareKit/blob/e893ce0830239b06986fffce9ee97aa2c5d83e3a/Sources/ParseCareKit/ParseCareKitUtility.swift#L73-L81

You can see info about these in the docs: http://parseplatform.org/Parse-Swift/api/Protocols/ParseObject.html#/s:10ParseSwift0A6ObjectPAAE14getJSONEncoder10Foundation0E0CyF

let us know if find any other issues!

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.

3 participants