parse-community / Parse-SDK-iOS-OSX

The Apple SDK for Parse Platform (iOS, macOS, watchOS, tvOS)
https://parseplatform.org
Other
2.81k stars 865 forks source link

LiveQuery canβ€˜t decode json to PHObject #1770

Open OspreyRen opened 5 months ago

OspreyRen commented 5 months ago

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

Environment

Client

Server

Database

parse-github-assistant[bot] commented 5 months ago

Thanks for opening this issue!