Code Monkey home page Code Monkey logo

Comments (3)

tanner0101 avatar tanner0101 commented on May 12, 2024 1

Here's an example of how to implement the custom reflection methods for desired effect.

struct User: Reflectable, Codable {
    var email: String
    var active: Bool
    
    static func reflectProperty<T>(forKey keyPath: KeyPath<User, T>) throws -> ReflectedProperty? {
        let name: String
        switch keyPath {
        case \User.email: name = "email"
        case \User.active: name = "active"
        default: return nil
        }
        return .init(T.self, at: [name])
    }
    
    init(from decoder: Decoder)throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.email = try container.decode(String.self, forKey: .email)
        self.active = try container.decodeIfPresent(Bool.self, forKey: .active) ?? false
    }
}

try XCTAssertEqual(User.reflectProperties().description, "[email: String, active: Bool]")
try XCTAssertEqual(User.reflectProperty(forKey: \.email)?.description ?? "n/a", "email: String")
try XCTAssertEqual(User.reflectProperty(forKey: \.active)?.description ?? "n/a", "active: Bool")

from core.

tanner0101 avatar tanner0101 commented on May 12, 2024

After digging into this a bit more, the issue seems unavoidable.

The problem is that false happens to be the value that the reflection decoder sets when a Bool property is active. (Bool has two cases which is the minimum to represent a "hi" and "lo" value. This is how the reflection decoder determines a hit or miss. Ignore the fact that false is a somewhat unintuitive "hi" value--they just need to be distinct).

The problem is that false is being set as a default value in the init(from:) method in question. When active is set to false, the reflection decoder gets a false positive that it has correctly identified the coding path for the property even though it hasn't. You can easily prove this by setting the default value to true instead of false. Reflection works normally.

TL;DR is that the algorithm used to power Codable reflection is not compatible with setting default values in a custom init(from:) decoder method. At least where those default values are the reflection decoder's "hi" value--which should be considered opaque.

Fortunately Codable reflection is protocol based and you can work around this issue by simply implementing the reflectProperty method manually. See https://api.vapor.codes/core/latest/Core/Protocols/Reflectable.html#/s:4Core11ReflectableP15reflectProperty6forKeyAA09ReflectedD0VSgs0F4PathCyxqd__G_tKlFZ.

Until Swift gets better reflection, this is really our best option. The alternative to using Codable-based reflection is having every model implement reflectProperty and reflectProperties (something the very early Fluent 3 alphas required) or using Strings for for query filtering. Both of those options suck worse...for lack of a better word.

Also worth noting that the init(from:) method can be done without the annoying do / catch by using contains. Although this still doesn't reflect the correct property.

init(from decoder: Decoder)throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.email = try container.decode(String.self, forKey: .email)
    if container.contains(.active) {
        self.active = try container.decode(Bool.self, forKey: .active)
    } else {
        self.active = false
    }
}

from core.

proggeramlug avatar proggeramlug commented on May 12, 2024

Thanks @tanner0101 !

The custom implementation of reflectProperty seems to be the most appropriate way to deal with it, though I like the contain approach as well.

I think I'll go with the contain approach for now, that seems most natural.

from core.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.