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

LiveQuery can‘t decode json to PHObject #1770

Open
OspreyRen opened this issue Jan 19, 2024 · 1 comment
Open

LiveQuery can‘t decode json to PHObject #1770

OspreyRen opened this issue Jan 19, 2024 · 1 comment
Labels
type:bug Impaired feature or lacking behavior that is likely assumed

Comments

@OspreyRen
Copy link

OspreyRen commented Jan 19, 2024

Issue Description

Hi guys.
When I subscribe object by LiveQuery, data can not be decoded to PHObject, code:

import Foundation
import ParseCore
import ParseLiveQuery

class Suggestion: PFObject, PFSubclassing {
    @NSManaged var title: String?
    @NSManaged var document: Document?

    class func parseClassName() -> String {
        return "Suggestion"
    }
}

class Suggestions {
    let subscribe: Subscription<Suggestion>
    
    init() {
        ParseLiveQuery.Client.shared.shouldPrintWebSocketTrace = true
        subscribe = ParseLiveQuery.Client.shared.subscribe(
            Suggestion.query()!.includeAll() as! PFQuery<Suggestion>
        )
        .handleEvent {
            log.I("recive \($1)") // never logging
        }
    }
}

Reiceived Json on WebSocket callback:

{
   "op":"delete",
   "clientId":"d81000da-4f64-4819-a470-8aade320322a",
   "requestId":2,
   "object":{
    // I think should have a "__type": "Object" here
      "document":{
         "__type":"Pointer",
         "className":"Document",
         "objectId":"EtZCPMTygF"
      },
      "title":"Suggestion 20",
      "createdAt":"2024-01-18T13:41:59.930Z",
      "updatedAt":"2024-01-18T13:41:59.930Z",
      "className":"Suggestion",
      "objectId":"aN94A8fXYB"
   }
}

I checked the code in PHDecoder and found that because of no "__type": "Object" in "object", it can not be decoded to PHObject or its subclass.

PHDecoder snippet(line 43):

- (id)decodeDictionary:(NSDictionary *)dictionary {
    NSString *op = dictionary[@"__op"];
    if (op) {
        return [[PFFieldOperationDecoder defaultDecoder] decode:dictionary withDecoder:self];
    }

    NSString *type = dictionary[@"__type"];
    if (type) {
        if ([type isEqualToString:@"Date"]) {
            return [[PFDateFormatter sharedFormatter] dateFromString:dictionary[@"iso"]];

        } else if ([type isEqualToString:@"Bytes"]) {
            return [PFBase64Encoder dataFromBase64String:dictionary[@"base64"]];

        } else if ([type isEqualToString:@"GeoPoint"]) {
            return [PFGeoPoint geoPointWithDictionary:dictionary];

        } else if ([type isEqualToString:@"Polygon"]) {
            return [PFPolygon polygonWithDictionary:dictionary];

        } else if ([type isEqualToString:@"Relation"]) {
            return [PFRelation relationFromDictionary:dictionary withDecoder:self];

        } else if ([type isEqualToString:@"File"]) {
            return [PFFileObject fileObjectWithName:dictionary[@"name"]
                                    url:dictionary[@"url"]];

        } else if ([type isEqualToString:@"Pointer"]) {
            NSString *objectId = dictionary[@"objectId"];
            NSString *localId = dictionary[@"localId"];
            NSString *className = dictionary[@"className"];
            if (localId) {
                // This is a PFObject deserialized off the local disk, which has a localId
                // that will need to be resolved before the object can be sent over the network.
                // Its localId should be known to PFObjectLocalIdStore.
                return [self _decodePointerForClassName:className localId:localId];
            } else {
                return [self _decodePointerForClassName:className objectId:objectId];
            }

        } else if ([type isEqualToString:@"Object"]) {
          // 🟨🟨🟨🟨🟨🟨🟨🟨
          // Should go here, but no "__type": "Object" in "object", so it can not be decoded to PHObject or its subclass.
          // 🟨🟨🟨🟨🟨🟨🟨🟨
            NSString *className = dictionary[@"className"];

            NSMutableDictionary *data = [dictionary mutableCopy];
            [data removeObjectForKey:@"__type"];
            [data removeObjectForKey:@"className"];
            NSDictionary *result = [self decodeDictionary:data];

            return [PFObject _objectFromDictionary:result
                                  defaultClassName:className
                                      completeData:YES
                                           decoder:self];

        } else {
            // We don't know how to decode this, so just leave it as a dictionary.
            return dictionary;
        }
    }

    NSMutableDictionary *newDictionary = [NSMutableDictionary dictionaryWithCapacity:dictionary.count];
    [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        newDictionary[key] = [self decodeObject:obj];
    }];
    return newDictionary;
}

And I added some workaround code to ParseLiveQuery/ParseLiveQuery/Internal/ClientPrivate.swift, everything is working fine. here is the code:

private func parseObject<T: PFObject>(_ objectDictionary: [String:AnyObject]) throws -> T {
    guard let _ = objectDictionary["className"] as? String else {
        throw LiveQueryErrors.InvalidJSONError(json: objectDictionary, expectedKey: "parseClassName")
    }
    guard let _ = objectDictionary["objectId"] as? String else {
        throw LiveQueryErrors.InvalidJSONError(json: objectDictionary, expectedKey: "objectId")
    }
    
    // 🟨🟨🟨new code 👇
    var dict = objectDictionary
    dict["__type"] = "Object" as AnyObject
    // 🟨🟨🟨new code 👆

    //  🟨🟨🟨 guard let object = PFDecoder.object().decode(objectDictionary) as? T else { change to 👇
    guard let object = PFDecoder.object().decode(dict) as? T else {
        throw LiveQueryErrors.InvalidJSONObject(json: objectDictionary, details: "cannot decode json into \(T.self)")
    }

    return object
}

So, is this a bug on the Server Side or the Client Side? Any better solution?
Thanks a lot.

Steps to reproduce

  • Create a class named "Suggestion" by Parse Dashboard with a column named "title"
  • Subscribe "Suggestion" class by LiveQuery on iOS Client Side
  • Create, Update, or Delete a "Suggestion" object on Parse Dashboard

Environment

Client

  • Parse ObjC SDK version: 2.7.3

Server

  • Parse Server version: 6.4.0
  • Operating system: macOS 14.1.2 (23B92)
  • Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): Local Dev Environment

Database

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

parse-github-assistant bot commented Jan 19, 2024

Thanks for opening this issue!

@mtrezza mtrezza added the type:bug Impaired feature or lacking behavior that is likely assumed label Jan 19, 2024
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

No branches or pull requests

2 participants