thoughtbot/Argo

In search of an operator for inconsistent JSON format. Does it exist?

btc opened this issue · 7 comments

btc commented

I am searching for an operator to help me concisely parse a JSON format that comes in two variants. Is this currently possible? If not, is it possible to define such an operator?

Sample JSON

{
  "name_tag": "Brian"
}
{
  "name-tag": "Brian"
}

Models

struct User {
    let nameTag: String
}

Ideally, I would write:

extension User: Decodable {
    static func decode(_ j: JSON) -> Decoded<User> {
        return curry(self.init)
            <^> j <| "name-tag" <?OPERATOR?> "name_tag"
    }
}

Argo Version

4.1.1

Dependency Manager

Carthage

This may work:

(j <| "name-tag") <|> (j <| "name_tag")

I'm not sure whether the parentheses are necessary.

btc commented

I tried to add that but couldn't get it to compile without expression complexity errors. Hmm.

In that case move the curried initializer to a separate value.

Or since you only have one value there you don't need the curried initializer at all.

I'm fascinated by the frequency with which you (@btc) are running into these compiler complexity errors. I'm really sorry about that.

@jshier is correct, the operator you are looking for is <|> (alternatively, using the .or(_:) instance method on Decoded), and I'd write it the way he did.

I'd definitely try saving the curried function off to an intermediate constant first, but if that doesn't work, you could also try saving the result of this decoding operation off to an intermediate constant:

extension User: Decodable {
  static func decode(_ j: JSON) -> Decoded<User> {
    let tag: Decoded<String> = (j <| "name-tag") <|> (j <| "name_tag")

    return curry(self.init)
      <^> tag
  }
}
btc commented

I'm fascinated by the frequency with which you (@btc) are running into these compiler complexity errors. I'm really sorry about that.

As am I. Nevertheless, this is such a great concept that I am willing to work around the rough edges.

Or since you only have one value there you don't need the curried initializer at all.

My real struct has more than 1 value. I just provided a minimal example so we could all get on the same page without unnecessary noise.

Thanks for your help everyone. The compiler became happy when I wrote it this way:

extension NowPlayingInfo: Decodable {
    static func decode(_ j: JSON) -> Decoded<NowPlayingInfo> {
        return curry(self.init)
            <^> j <| "title"
            <*> j <| "artist"
            <*> j <| "album"
            <*> j <| "station-tag"
            <*> (j <| "media-type" <|> j <| "media_type")
            <*> NowPlayingInfo.Artwork.decode(j)
    }
}

The compiler also likes the following!

func either<T: Decodable>(_ j: JSON, _ field: String, _ alt: String) -> Decoded<T> where T == T.DecodedType {
    return j <| field <|> j <| alt
}

extension NowPlayingInfo: Decodable {
    static func decode(_ j: JSON) -> Decoded<NowPlayingInfo> {
        return curry(self.init)
            <^> j <| "title"
            <*> j <| "artist"
            <*> j <| "album"
            <*> j <| "station-tag"
            <*> either(j, "media-type", "media_type")
            <*> NowPlayingInfo.Artwork.decode(j)
    }
}

Thanks, everyone!

btc commented

Also:

func eitherO<T: Decodable>(_ j: JSON, _ field: String, _ alt: String) -> Decoded<T?> where T == T.DecodedType {
    return  j <|? field <|> j <|? alt
}