Skip to content

Yams.dump function Any object parameter casted as NodeRepresentable fails #299

@ABridoux

Description

@ABridoux

Hi! I encountered an error while using the Yams.dump(object:) function because the represent(:) tries to cast an Any value to NodeRepresentable. And it seems to be a Swift problem.

Problem exposition

The error comes from the deserialisation of a Data object to Any. Here is the code that demonstrates that (and the attached playground).

import Foundation

protocol StubProtocol {}
extension Array: StubProtocol {}

let ducks = ["Riri", "Fifi", "Loulou"]
let data = try JSONEncoder().encode(ducks)
let deserializedDucks = try JSONSerialization.jsonObject(with: data)
let castedDeserializedDucks = deserializedDucks as! [String]

print("StubProtocol conformance of 'ducks': \(ducks is StubProtocol)") // true
print("StubProtocol conformance of 'deserializedDucks': \(deserializedDucks is StubProtocol)") // false
print("StubProtocol conformance of 'castedDeserializedDucks': \(castedDeserializedDucks is StubProtocol)") // true

Only when casting the value from Any to [String] the cast to the protocol will work. The same goes for the cast in the represent(:) function:

if let representable = value as? NodeRepresentable

The casting would work only if the Any value is properly casted. But then it will fail with this value children since the call is recursive (e.g. with an array or a dictionary).

Solution

Recursive cast

A solution that the developer can implement is a recursive cast of the Any value to NodeRepresentable. For instance, here is an implementation in Scout.

func castToNodeRepresentable(_ value: Any) -> NodeRepresentable {
    if var value = value as? [String: Any] {
        value.forEach { value[$0.key] = castToNodeRepresentable($0.value) }
        return value
    } else if let value = value as? [Any] {
        return value.map(castToNodeRepresentable)
    } else if let value = value as? Int {
        return value as NodeRepresentable
    } else if let value = value as? Double {
        return value as NodeRepresentable
    } else if let value = value as? Date {
        return value as NodeRepresentable
    } else if let value = value as? Bool {
        return value as NodeRepresentable
    } else {
        return String(describing: value) as NodeRepresentable
    }
}

This does work, especially as the serialisation handles a limited set of types.
But then it seems strange to have to cast an Any value to a NodeRepresentable if the function dump(object:) expects Any.
One solution could be for the library to perform this recursive cast on behalf of the developer. Thus making sure the provided Any value conforms to the NodeRepresentable protocol. But this approach has some weaknesses:

  • performing this operation can take some time on large chunks of data
  • it does not allow customisation of the process. For example the developer could prefer to throw an error rather than fall back on a String.

Requiring a NodeRepresentable

Another solution could be to change the dump(object:) parameter type from Any? to NodeRepresentable

  • This would make the developer aware of the requirement rather than hiding it. Thus making them responsible for conforming Any to this protocol, while also allowing customisation.
  • Requiring a NodeRepresentable prevents a runtime error that can be misleading. After all, if the represent(:) function has to work with a NodeRepresentable object, why wouldn't it ensure it rather than handling an Any value? In fact, I first thought that the problem came from the YAMS library since I was passing it an Any value and there were no requirements on this value. Then I realised the problem came from the deserialisation process of Foundation.

Please let me know if this solution seems valid. If so, I would open a pull request with the proposed modifications.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions