izzyparser-ios icon indicating copy to clipboard operation
izzyparser-ios copied to clipboard

Feature Request: Optional value-type attributes serialization support

Open GDXRepo opened this issue 4 years ago • 2 comments

In the current version there is no possibility to use optional value-type attributes when serializing a Resource. For example, backend and client app communicates between each other using the following object:

{
  "data": {
    "attributes": {
      "value1": 35, 
      "value2": false,
      "value3": "stringValue"
    }, 
    "id": "B2F3A61B-CBF3-4F92-A529-013DD548BB33", 
    "type": "someType"
  }
}

But the two properties in there - value1 and value2 - are optional by backend design and may not be sent to client app at all, for example, backend can send this object like this:

{
  "data": {
    "attributes": {
      "value3": "stringValue"
    },
    "id": "B2F3A61B-CBF3-4F92-A529-013DD548BB33", 
    "type": "someType"
  }
}

So our Resource-based class will look like this:

@objcMembers public final class SomeDTO: Resource {
    
    public override class var type: String {
        return "someType"
    }
    
    public var value1: Int? // non-@objc property, because Int?, Bool? and so on can't be represented in Objective-C
    public var value2: Int?
    @objc public var value3 = ""
    
    public required init(id: String) {
        super.init(id: id)
    }
}

If I use default izzy.serialize(resource) method I will get invalid JSON:API structure with missing value1 and value2 properties.


If my object has only one attribute (or one relationship) property - I can use izzy.serializeCustom(_:attributeKey:attributeValue:) method to solve the issue, like this:

@objcMembers public final class SomeDTO: Resource {
    
    public override class var type: String {
        return "someType"
    }
    
    public var value1: Int?
    @objc public var value2 = ""
    
    public required init(id: String) {
        super.init(id: id)
    }

    public func serialized() -> [String: Any] {
        guard let value1 = value1 else {
            return Izzy().serialize(resource: self)
        }
        return Izzy().serializeCustom(resource: self, attributeKey: "value1", attributeValue: value1)
    }
}

// ...

let dto = SomeDTO(value1: 25, value2: "abc")
print(dto.serialized()) // <-- OK

And that's OK. But if my Resource object have two optional value-type attributes (or one attribute and one relationship object, for example) - I have no ability to serialize automatically it at all. Of course I can do it manually by modifying resulting dictionary, but this is not so good as it could be :)

Please provide method to specify many attributes and many relationships in one serialization method to solve this problem. Maybe use Builder pattern to provide ability to build JSONAPI serialized object manually by appending custom attributes and/or custom relationships values for such problematic objects.

Thank you!

GDXRepo avatar Jul 02 '21 01:07 GDXRepo

Hi, so the issue here is the bridging types between ObjC and Swift. Izzy leverages ObjC reflection to get property names from an object, hence @objMemebers.

Since Int and therefore Int? isn't representable in ObjC those properties get ignored when serializing the object.

It looks like Swift compiler doesn't generate errors when using @objcMembers on a class that has types that cannot be represented in ObjC, but if you explicitly put @objc in front of your Int? properties you will get:

Property cannot be marked @objc because its type cannot be represented in Objective-C

For now, an easy way to fix this is to use NSNumber to represent any numerical or boolean properties.

@objcMembers public final class SomeDTO: Resource {

    public override class var type: String {
        return "someType"
    }

    public var value1: NSNumber?
    public var value2: NSNumber?
    public var value3: String?

    public required init(id: String) {
        super.init(id: id)
    }
}

Your suggestion to add a builder pattern to serialize custom attributes and relationships would be an improvement to an API, and we will consider adding it in the future.

What do you think of the following example?

Izzy().custom(resource)
    .attribute("value1", value: 1)
    .attribute("value2", value: 2)
    .attribute("value3", value: "3")
    .relationship("related", object: relatedResource)
    .serialize()

indiche avatar Jul 02 '21 11:07 indiche

Yeah, your builder example looks good. I will wait for your realization. Thank you again, your library is very useful. Waiting for a new version of the Izzy)

GDXRepo avatar Jul 02 '21 13:07 GDXRepo