evermeer / AlamofireJsonToObjects

An Alamofire extension which converts JSON response data into swift objects using EVReflection
Other
161 stars 28 forks source link

Custom parser #17

Closed sagits closed 8 years ago

sagits commented 8 years ago

Hi, i have the following class:

class EventModel : MyModel {
    var name : String?
    var dateStart : String?
    var dateEnd : String?
    var allDay : Int = 0
    var objectFull : NSObject?
    var objectType : String?

 override func setValue(value: AnyObject!, forUndefinedKey key: String) {
        switch key {
        case "objectFull":
            if (objectType == "task") {
                objectFull = (value as? TaskModel)!
            }
            else if (objectType == "sale") {
                objectFull = (value as? SaleModel)!
            }
            break
        default:
            print("---> setValue '\(value)' for key '\(key)' should be handled.")
        }
    } 

}

I know i could solve this with generics, but i receive an array of events from my server, so i cant instantiate it with generics. This approach (NSObject) isnt working. May you help me please? Thanks in advance.

evermeer commented 8 years ago

Hi, since there is a property named objectFull the function setValue forUndefined key will not be called for the key objectFull. (the key is not undefined.)

I think the best approach for this is by creating a base class for your objectFull objects. EVReflection has support for this. You can override the function getSpecificType where you make sure the correct type is returned. There is a unit test available that will show you how this can be used. See: https://github.com/evermeer/EVReflection/blob/7b4b3b31f3f9a5a3166cc32acc5501d4bd3ae2c9/EVReflection/EVReflectionTests/EVRelfectionEnheritanceTests.swift#L70-L70 make sure you base the type selection on what's in the dictionary and not based on existing properties in the object. The test will show you how you can do this.

evermeer commented 8 years ago

Just let me know of you have any questions

sagits commented 8 years ago

Hi, thank you, it worked. How can i access the returned object? I have tried with objectFull as! TaskModel but it gives an exception. Using objectFull.valueForKey(keyName) worked, but is there another way? Thank you in advance.

evermeer commented 8 years ago

Hi @sagits, Are you still using the setValue forKey for setting the fullObject? If so, then It will be called with a dictionary and not with a parsed object. You could still set it with something like: objectFull = TaskObject(dictionary: value)

If you are using the getSpecificType code, then the object should already be set correctly.

sagits commented 8 years ago

Hi @evermeer. Im using the getSpecificType() approach. But on the editor the fullObject is still a FullObjectModel, and although on compile time it has all the TaskModel or SaleModel fields, its still declared as as FullObjectModel on the debugger (because the property type is FullObjectModel).

evermeer commented 8 years ago

I just added some extra checks in the inheritance test. There it works. Here is the code that I now have in place. As you can see I go from object to json to an object again and then test if the values are as the original object.


    func testEnheritance() {
        // Build up the original object
        let quz = Quz()
        quz.fooBar = Bar()
        quz.fooBaz = Baz()
        quz.fooFoo = Foo()
        quz.fooArray = [Bar(), Baz(), Foo()]

        // The object JSON
        let json = quz.toJsonString()
        print("Original JSON = \(json)")

        // Deserialize a new object based on that JSON
        let newObject = Quz(json: json)

        XCTAssertEqual((newObject.fooBar as? Bar)?.justBar, "For bar only", "fooBar is not Bar?")
        XCTAssertEqual((newObject.fooBaz as? Baz)?.justBaz, "For baz only", "fooBaz is not Baz?")
        XCTAssertEqual(newObject.fooArray.count, 3, "array should contain 3 objects")
        if newObject.fooArray.count == 3 {
            XCTAssertEqual((newObject.fooArray[0] as? Bar)?.justBar, "For bar only", "fooBar is not Bar?")
            XCTAssertEqual((newObject.fooArray[1] as? Baz)?.justBaz, "For baz only", "fooBaz is not Baz?")
            XCTAssertEqual(newObject.fooArray[2].allFoo, "all Foo", "should just be a Foo?")
        }

        // The new object JSON
        let newJson = newObject.toJsonString()
        print("New JSON = \(newJson)")

        // The original and new JSON Should be the same
        XCTAssertEqual(json, newJson, "The json should be the same after serialisation and deserialization")
    }

}

class Quz: EVObject {
    var fooArray: Array<Foo> = []
    var fooBar: Foo?
    var fooBaz: Foo?
    var fooFoo: Foo?
}

class Foo: EVObject {
    var allFoo: String = "all Foo"

    // What you need to do to get the correct type for when you deserialize inherited classes
    override func getSpecificType(dict: NSDictionary) -> EVObject {
        if dict["justBar"] != nil {
            return Bar()
        } else if dict["justBaz"] != nil {
            return Baz()
        }
        return self
    }
}

class Bar : Foo {
    var justBar: String = "For bar only"
}

class Baz: Foo {
    var justBaz: String = "For bad only"
}
sagits commented 8 years ago

It worked, my mistake. Thank you for helping.