Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using an alternate decode for an array of enums #284

Closed
jshier opened this issue Nov 4, 2015 · 6 comments
Closed

Using an alternate decode for an array of enums #284

jshier opened this issue Nov 4, 2015 · 6 comments
Labels

Comments

@jshier
Copy link
Contributor

jshier commented Nov 4, 2015

I have a Day type:

enum Day: Int {
    case Sunday = 0
    case Monday = 1
    case Tuesday = 2
    case Wednesday = 3
    case Thursday = 4
    case Friday = 5
    case Saturday = 6

    static let Weekdays: [Day] = [.Monday, .Tuesday, .Wednesday, .Thursday, .Friday]
    static let Weekends: [Day] = [.Saturday, .Sunday]
    static let Daily: [Day] = Weekdays + Weekends
}

Works great, gets the automatic decode from the RawRepresentable extension. However, in the same API, days are sometimes represented as strings (e.g. "1") rather than the raw ints that get decoded automatically. Obviously I have to make a new decode function, but I'm unsure how to apply when decoding an array of Days coming back with string values. I came up with a decode that can convert an array of strings into an array of Days, which is gross:

static func decodeFromStrings(strings: [String]) -> Decoded<[Day]> {
    let ints = strings.flatMap { Int($0) }
    if ints.count != strings.count {
        return .customError("Could not convert all Strings to Ints for [Day].")
    }

    let days = ints.flatMap { Day(rawValue: $0) }
    if days.count != strings.count {
        return .customError("Could not convert all Ints to Days for [Day].")
    }

    return pure(days)
}

Which I call like this:

<*> (j <|| ["key1", "key2", "day"] >>- Day.decodeFromStrings)

Creating a decode variant that turns strings into ints and then decodes the enum is easy, but how would I call this function on the array of strings I get when parsing?

@gfontenot
Copy link
Collaborator

Fantastic question, this is something we should add documentation about.

Decoded actually provides an operator for this: <|>. That's the alternate operator. If argument on the left hand side of the operator is .Failure, it returns the Decoded object on the right hand side. This is useful for providing fallback decoding operations like this.

Essentially, you'd end up with something like this:

<*> (j <|| ["key1", "key2", "day"]) <|> (j <|| ["key1", "key2", "day"] >>- Day.decodeFromStrings)

So it'll try to decode from Ints first, and if that fails, it'll try to decode from Strings, and return that.

You might even go further and abstract out a couple helper methods:

func decodeDays<T: Decodable where T.DecodedType == T>(j: JSON) -> [T] {
  return j <|| ["key1", "key2", "day"]
}

func decodeStringDays(j: JSON) -> Decoded<[Day]> {
  return decodeDays(j) >>- Day.decodeFromStrings
}

Then your parser would be simplified a bit:

<*> decodeDays(j) <|> decodeStringDays(j)

@owensd
Copy link

owensd commented Nov 4, 2015

Just an FYI, weekdays/weekends are not statically defined; they vary based on locales.

@hyperspacemark
Copy link

While true, that is completely irrelevant to the discussion about Argo's Alternative operator.

@jshier
Copy link
Contributor Author

jshier commented Nov 4, 2015

@owensd They're static in this API, as there is no localization at all. That is just the tip of the iceberg of bad practice for this API.

@gfontenot Thanks for the information, but I think I was clear. The endpoint that returns the ints as strings is completely separate from the one that properly returns them as ints, so there's no use for the alternate operator. My question was more about the appropriate syntax to pull a array of an enum from an array of strings while calling a custom decode function that doesn't operate at an array level. If you think my current solution is okay, that's fine, I just thought there might be a better way I hadn't figured out.

@tonyd256
Copy link
Contributor

tonyd256 commented Nov 4, 2015

I might override the decode function to something like this:

static func decode(j: JSON) -> Decoded<Day> {
  switch j {
  case .String(s): // convert to int and pass into rawType constructor
  case .Number(n): // try to pass into rawType constructor (cast as Int)
  default: .typeMismatch ...
  }
}

@jshier
Copy link
Contributor Author

jshier commented Nov 4, 2015

@tonyd256 Thanks. I had wanted to keep the automatic decode, but this is probably the easiest solution. I certainly like it better than my array solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants