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

Is there a way to parse two dimensional arrays? #166

Closed
lfrahm opened this Issue Jul 10, 2015 · 13 comments

Comments

Projects
None yet
6 participants
@lfrahm

lfrahm commented Jul 10, 2015

Hey there!

First of all thanks for providing such a convenient way of transforming JSON data into models! It is super helpful.

But I'm wondering how I can parse more-dimensional arrays. I can't just wrap it into a new model since the deeper JSON text doesn't have a field, and unfortunately there is no <||| operator.

So is there a way to parse something like:
{ "data":[ [0,1], [2,3], [4,5] ] }

@lfrahm

This comment has been minimized.

lfrahm commented Jul 10, 2015

So here is what I can come up with. I would appreciate some feedback.

For file Operators.swift

infix operator <||| { associativity left precedence 150 }
infix operator <|||? { associativity left precedence 150 }

// MARK: Arrays of Arrays

// Pull array of arrau from JSON
public func <||| <A where A: Decodable, A == A.DecodedType>(json: JSON, key: String) -> Decoded<[[A]]> {
    return json <| key >>- decodeArrayOfArray
}

// Pull optional array of array from JSON
public func <|||? <A where A: Decodable, A == A.DecodedType>(json: JSON, key: String) -> Decoded<[[A]]?> {
    return .optional(json <||| key)
}

// Pull embedded array of array from JSON
public func <||| <A where A: Decodable, A == A.DecodedType>(json: JSON, keys: [String]) -> Decoded<[[A]]> {
    return json <| keys >>- decodeArrayOfArray
}

// Pull embedded optional array of array from JSON
public func <|||? <A where A: Decodable, A == A.DecodedType>(json: JSON, keys: [String]) -> Decoded<[[A]]?> {
    return .optional(json <||| keys)
}

For file file StandardTypes.swift

public func decodeArrayOfArray<A where A: Decodable, A == A.DecodedType>(value: JSON) -> Decoded<[[A]]> {
  switch value {
    case let .Array(a):
      var twoD: [[Decoded<A>]] = []
      for elem in a {
        switch elem {
        case let .Array(b): twoD.append(A.decode <^> b)
        default: return typeMismatch("Array", value)
        }
      }
      return sequence(twoD)
    default: return typeMismatch("Array", value)
  }
}

and for file sequence.swift

func sequence<T>(xs: [[Decoded<T>]]) -> Decoded<[[T]]> {
    return reduce(xs, pure([])) { accum, elem in
        curry(+) <^> accum <*> (pure <^> sequence(elem))
    }
}

Thanks for the awesome framework and the help!

@tonyd256

This comment has been minimized.

Contributor

tonyd256 commented Jul 14, 2015

@lfrahm This all looks fine, but I don't think we should add a new operator. You can see that this is a never ending cycle depending on how many dimensioned arrays you want to parse. There has got to be another way to do this. Let me think about it and I'll get back to you.

@tonyd256

This comment has been minimized.

Contributor

tonyd256 commented Jul 15, 2015

@lfrahm So I actually found this easy to do once we make sequence public. I'm going to make a PR for that but this is what it would look like:

let a: Decoded<[JSON]> = j <|| "data"
let a2: Decoded<[[Int]]> = a >>- { sequence(decodeArray <^> $0) }

Or can be just one line in a decode function

static func decode(j: JSON) -> Decoded<SomeType> {
  return create
    <^> ...
    <*> (j <|| "data" >>- { sequence(decodeArray <^> $0) })
    <*> ...
}
@tonyd256

This comment has been minimized.

Contributor

tonyd256 commented Jul 16, 2015

@lfrahm #170 was merged. You can use sequence now with version 1.0.4. The code I posted above should solve 2-dimensional array decoding. Let me know if that works for you.

@tonyd256

This comment has been minimized.

Contributor

tonyd256 commented Aug 10, 2015

@lfrahm Any updates here? Did making sequence public help?

@tonyd256

This comment has been minimized.

Contributor

tonyd256 commented Aug 21, 2015

@lfrahm Closing this for now. Feel free to keep commenting here for related questions or make a new issue for other things.

@tonyd256 tonyd256 closed this Aug 21, 2015

@ulrichzoltan

This comment has been minimized.

ulrichzoltan commented Jan 18, 2016

Hi there,

I ran into the same problem of modeling two dimensional arrays. I tried out the above suggestions, as follows, but with the current version of the library (2.2.0) the compilation fails.

struct Matrix {
    let data: [[Int]]
}

import Curry
import Argo

extension Matrix : Decodable {
    static func decode(j: JSON) -> Decoded<Matrix> {
        return curry(Matrix.init) <^>
            (j <|| "data" >>- { sequence(decode <^> $0) })
    }
}

The compiler says:

Matrix.swift:24:29: error: cannot convert value of type '(data: [[Int]]) -> Matrix' to expected argument type '(_) -> _'

What would be the way to do it with the latest version?

Thank you.

@deadlyfingers

This comment has been minimized.

deadlyfingers commented Aug 4, 2016

should it be sequence decodeArray where you have decode as @tonyd256 mentioned above?

@deadlyfingers

This comment has been minimized.

deadlyfingers commented Aug 5, 2016

@tonyd256 is the the right way to use sequence and decodeArray for two dimensional array (arrayOfThings)? I'm getting cannot invoke 'curry' error.

struct Model {
    let thing: Int
    let things: [Int]
    let arrayOfThings: [[Int]]
}

extension Model: Decodable {
    static func decode(j: JSON) -> Decoded<Model> {
        return curry(Model.init)
            <^> j <| "thing"
            <*> j <|| "things"
            <*> (j <|| "arrayOfThings" >>- { sequence(decodeArray <^> $0) })
    }
}
@tonyd256

This comment has been minimized.

Contributor

tonyd256 commented Aug 5, 2016

@deadlyfingers you're right. It should be decodeArray. Your example is spot on! The only thing "wrong" is the use of the operator <^>. Any when I say wrong, I mean that you are correct that you need to map over the $0 but $0 is an array so you need the array implementation of <^> which is not found in Argo. It is found in Runes. You can import Runes and that should work or you can use the map function instead like so: (j <|| "arrayOfThings" >>- { sequence($0.map(decodeArray)) })

@gruenekampfente

This comment has been minimized.

gruenekampfente commented Aug 9, 2016

Hello everybody,

I've tried to solve this like @tonyd256 said, but now i get a Build Error "Command /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc failed with exit code 1".
Is there any solution for this?

@gfontenot

This comment has been minimized.

Collaborator

gfontenot commented Aug 9, 2016

@gruenekampfente Can you try cleaning the build and maybe nuking derived data? That error is pretty vague and could be popping up for any number of reasons.

@gruenekampfente

This comment has been minimized.

gruenekampfente commented Aug 11, 2016

yes i've tried that but the real solution was to replace the above line of code with this
<*> (json <|| "arrayOfThings" >>- { sequence($0.map { [String].decode($0) }) })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment