How to exclude members from parsing

So ParsableArguments requires that all members use one of their property wrappers.

I'm trying to add defaulted properties that won't participate in the parsing.
In so far, I've used a wrapper that ignores decoding

@propertyWrapper
struct Undecoded<Value>: Decodable {
    var storage: Value?

    init() { }
    init(from decoder: Decoder) throws { }

    var wrappedValue: Value {
        get { storage! }
        set { storage = newValue }
    }
}

struct Command: ParsableCommand {
  @Argument() var foo: Int
  @Undecoded let unparsed: Int

  mutating func validate() throws {
    unparsed = 4
  }
}

Is there any caveat in doing this? Any idea on improving this?
Ideally I'd want to do:

struct Command: ParsableCommand {
  @Argument() var foo: Int
  let unparsed = 4
}

I could also separate the Parsing from the Running logic, but it doesn't seem to be what ArgumentParser is promoting.

Huh. I need to learn more about how Decodable synthesis works. Your ideal code actually works as is:

struct Guess: ParsableCommand {
  @Argument() var guess: Int
  let magic = 4
  
  func run() {
    if guess > magic { print("too high") }
    else if guess < magic { print("too low") }
    else { print("you got it!") }
  }
}
% guess 5
too high
% guess 3
too low

...but if I change the let to var, it tries to decode a value for the magic property, and fails:

% guess 5
Error: Missing expected argument
Usage: guess <guess>
1 Like

That us both.

Turns out the excluding members also need to be Decodable, despite not being used.

It’s probably because you have a let property with an initial value so it cannot be overwritten during decoding, so it gets skipped.

4 Likes

Anyway to achieve this with var variables?

What happens if you use a var?

Pretty much this:


As said, I've been using @Unparsed, but it's less than ideal.

So you want to use a var but don’t want it to be decoded?

Yes, it has a default value. Ideally I'd have

var a = 3

I also try to get to something like this

@Undecoded(initialValue: 3) var a: Int

but it doesn't seem to avoid the decoding phase used by arg-parser.

Maybe you can exclude it by specifying the CodingKeys explicitly?

struct DecodableThing: Decodable {
	var foo: Int = 2
	let bar: String

	private enum CodingKeys: String, CodingKey {
		case bar
	}
}

let jsonString = """
{ "foo": 1, "bar": "hello" }
"""

let decodedThing = try! JSONDecoder().decode(DecodableThing.self, from: Data(jsonString.utf8))
print(decodedThing.foo) // 2
3 Likes

That‘s what I mentioned on Slack as being very annoying. I‘m not sure this was intended.

Encountered this myself. Pretty nasty. The error message is totally unhelpful, so I spent a couple of hours trying to figure out what was wrong with my @Options and @Flags. Only when I started putting breakpoints in the ArgumentParser library itself, on the error message line, did I discover the problem was a var that was not even marked as a argument.

Even when you discover that problem, it is a bit nasty: you can change to a "let" property, but then the compiler throws up warnings about them being ignored during coding. If you use a var, argument parser doesn't work.

It would at least be very useful if the name of the non-argument property that is causing the issue were named in the error message. For some reason it doesn't print, leaving the user in the dark.

Hello interested parties — the newest release (0.4.0) includes a change that allows var properties that aren't declared with a parsing property wrapper. Please try it out and open an issue if you have any problems!

2 Likes

Hello @nnnnnnnn do you have an example of that change you mentioned in 0.4.0 ?

I am using 1.2.3 now and when I have a var attribute that is not an @Option, I receive this error at the struct level : Type 'GenerateToken' does not conform to protocol 'Decodable'

Here is the offending line in my AsyncParsableCommand struct:

        // initialize logger
        var log : Logger = Logger(label: "generate-token")

For the Codable conformance to work properly, your Logger type needs to be Codable — is that the case?

Thank you for the quick response. Nope it is not. It is the logger provided by swift-log project. Any workaround to use types that are not Codable ?

Right now, I ended up putting my log at top-level, outside of the struct, but this is not ideal as it is shared by all structs in the file.

fileprivate var log = Logger(label: "generate-token")