3

I'm trying to get used to some haskell libraries by solving some online practice problems.

I have some code which outputs this

Object (fromList [("ABC", String "123")])

It may also be

Object (fromList [("123", String "ABC")])
Object (fromList [(String "123", String "ABC")])
Object (fromList [("123", "ABC")])

what I need to extract is "123"

using .: which has type (.:) :: FromJSON a => Object -> Text -> Parser a to extract value given key raises this error

• Couldn't match type ‘Value’ with ‘HashMap Text Value’                                                         
  Expected type: Object
    Actual type: Value

My best guess is that I'm going to have to write a parser, but I have no idea how to go about doing that or what to look for.

Code that produced the error :

x <- (eitherDecode <$> simpleHttp url) :: IO (Either String DataSet)
  case x of
    Left er   -> print er
    Right an -> do
      let l = S.toList (data1 an)
      print $ l .: "ABC"

where DataSet is defined like this

newtype DataSet = DataSet {
                   data1  :: Object
                   } deriving (Show, Generic)

If I were to replace

print $ (Data.List.head l) .: "ABC"

with just

print $ (Data.List.head l)

I get

Object (fromList [("ABC", String "123")])
atis
  • 881
  • 5
  • 22
  • You haven't included the code that actually produces the error - could you please do so? It's possible, for example, that you have made a simple typo, that we cannot see here. – AJF Oct 17 '18 at 19:14
  • @AJFarmar added in the code that produced the error. – atis Oct 17 '18 at 19:34
  • Great, thank you. I'm not sure if I've found the issue, but can I ask why you write `let l = L.toList (data1 an)` instead of `let l = data1 an`? That seems like it could be the issue here. – AJF Oct 17 '18 at 20:09
  • That's because `data1 an` outputs an `Object` type. `an` contains a lot of `Object (fromList [("ABC", "123")])`. Applying `toList` on it lets me convert it to a list and extract out each one at a time. – atis Oct 17 '18 at 20:16
  • From what I understand `Object (fromList [("ABC", "123")])` has the type `Value` and just `fromList [("ABC", "123")]` has the type Object. So I think there should be a way to extract `Object` out of the `Value` type – atis Oct 17 '18 at 20:20
  • That may be so, but still `l` is a list, and you're writing `l .: "ABC` which is not well-typed, so why are you writing that? – AJF Oct 17 '18 at 20:27
  • @AJFarmar Sorry that was my bad. I forgot to add `Data.List.head` in front of l. Fixed – atis Oct 17 '18 at 20:51

3 Answers3

2

Object is one of several constructors of the Value type

Haskell Constructor | JSON Syntax
Object              | {"key": "value"}
String              | "hello"
Number              | 123
Array               | [1, 2, 3]

Note that in this case, the constructor Object is not a constructor of the type Object. [Note at end.]

The error comes from passing a Value to some function expecting an Object. You will need to define what the program should do if it encounters any of the other cases.

Or since you have data1 an :: Object, you can lookup the key you want in that. I'm not sure what the type of S.toList is, but you seem to be converting your Object to a Value and then passing it to .: which requires an Object.

Final note: Object (fromList [("ABC", String "123")]) is a single Value with a single Object with one key-value pair. fromList is a way to create Objects from their parts (instead of by parsing JSON strings).

bergey
  • 3,041
  • 11
  • 17
  • I'm still a beginner in haskell so this might be an odd question but wouldn't looking up key need the type to be `Object` instead of `Value` which is the result of the last `print` statement. Applying `data1` on `an` gives me a bunch of objects. Applying `toList` on it lets me choose which element I want. And from inside that element I need to extract the `"123"`. `toList data1 an` is equivalent to `[Value, Value, Value,....]`. Applying `head` means I ultimately get just `Value`. – atis Oct 17 '18 at 21:09
  • It would help if you posted your entire program. `.:` requires an Object. According to the definition you gave of `data1 :: DataSet -> Object`, `data1 an :: Object`. What are you seeing that suggests `data1 an` is "a bunch of objects"? Whence are you importing `toList`? There are several functions of that name. – bergey Oct 18 '18 at 01:00
0

One quick and dirty way of getting things out of Value type is to

encode Value as ByteString

encode has type encode :: ToJSON a => a -> ByteString

so here in your code

...
  case x of
    Left er   -> print er
    Right an -> do
      let l = S.toList (data1 an)
          x = (encode . snd) l  -- you can replace snd with fst if you want "ABC" instead of "123"
          y = decode x :: Maybe (HashMap String String)
          case y of
              Nothing -> print "got nothing"
              Just a -> print $ Data.HashMap.Strict.toList a

Which will output a list like this:

[("123")]

Now you can extract values with simple functions.

Hope that helps.

To know more about how you'd go about parsing JSON file better I would recommend giving https://artyom.me/aeson a good read.

0

Here are some ways you can unwrap the Object constructor from the Value data type.

You could create a function to unwrap:

unwrapValue :: Value -> Object
unwrapValue (Object x) = x
unwrapValue _ = error "No Object available"

Notice this function will return an error because there are possibilities where Value will not be an Object.

Also, don't get confused by Object being both a constructor of Value and a type in aeson!

You could unwrap inline as well but it is also not safe, meaning it can cause runtime errors. For example:

getNum :: Array -> Either String Scientific
getNum someArray = flip parseEither someArray $ \arr -> do
  let Just (Object obj) = arr !? 1 -- Unsafe unwrap Object constructor from Value (also unwraps Just constructor from Maybe)
  (Number num) <- obj .: "myNumber" -- Unsafe unwrap Number constructor from Value

  return num
Dave
  • 628
  • 7
  • 14