Lukáš Křivka
Lukáš Křivka

Reputation: 983

Aeson - simply decode ByteString -> Value

Sometimes Haskell can be really frustrating :) I'm searching for any function to simply convert ByteString into a Value. Everything seems to be only speaking about converting directly ByteString into a final type.

My reason to do this is to convert the JSON into Value and then "pre-extract" part of the Value that contains the "real data" and only pass the "real data" (one of many types) for specific type decoding so this part is not repeated in every parseJSON instance.

EDIT: Thanks to everyone for answers, I forgot to show the real problem, maybe there is a better solution:

So, I'm building API client and the API returns everything wrapped in "data" object.

GET Dataset

{
  "data": {
    "id": "WkzbQMuFYuamGv3YF",
    "name": "d7b9MDYsbtX5L7XAj",
    ...
  }
}

I parse this into

data Dataset = Dataset {
  name :: String,
  id :: String,
  ...
}

Then there is more complicated type of response with pagination GET List Datasets

{
  "data": {
    "total": 2,
    "offset": 0,
    "limit": 1000,
    "desc": false,
    "count": 2,
    "items": [
      {
        "id": "WkzbQMuFYuamGv3YF",
        "name": "d7b9MDYsbtX5L7XAj",
        ...
      },
      ...
     ]
  }
}

For which I use more generic type as there is pagination of other things too

data PaginationList a = PaginationList {
    total :: Int,
    offset :: Int,
    limit :: Int,
    desc :: Bool,
    count :: Int,
    items :: [a]
} deriving Show

Now, this composes nicely and the Dataset inside parses automatically except for that "data" wrapper that I have to remove in every parsing. So this naive attempt doesn't work since in items :: [a] (where a becomes Dataset) I would try to unwrap every Dataset from "data".

Thus the solution I thought of is to pre-parse the JSON into Value to get rid of the "data" and then all the types will compose nicely.

Upvotes: 3

Views: 1127

Answers (2)

Thomas M. DuBuisson
Thomas M. DuBuisson

Reputation: 64740

Everything seems to be only speaking about converting directly ByteString into a final type.

What makes "a finaly type" special? Can Value be "final type"?

I think you're talking about the FromJSON class, which Value is already an instance.

You mention how Haskell can be frustrating and you've been searching for a function. Perhaps what you need isn't just a solution (decode as mentioned by @WillemVanOnsem) but also learning how to search.

How to Search

First, look at the documentation of type you want and its instances If you're at all familiar with the library you should recognize the important instances of ToJSON and FromJSON, which yield what you desire.

If the instances do not suffice then use Hoogle as Chris commented. In this case you could tell hoogle to look just at Aeson and the type of the function you'd like: +aeson ByteString -> Value.

Upvotes: 2

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476594

Value is an instance of FromJSON, so you can make use of decode :: FromJSON a => ByteString -> Maybe a:

Prelude Data.Aeson> decode "{\"menu\": {\"id\": \"file\", \"value\": \"File\", \"popup\": {\"menuitem\": [1,4,2,5]}}}" :: Maybe Value
Just (Object (fromList [("menu",Object (fromList [("popup",Object (fromList [("menuitem",Array [Number 1.0,Number 4.0,Number 2.0,Number 5.0])])),("value",String "File"),("id",String "file")]))]))

So in fact there is nothing to define at all, you can use decode to decode to a Value as well. You however probably should specify the siganture to make that clear, so decode "4" :: Maybe Value for example.

The return type of decode is a Maybe Value, since it is not said that the ByteString you provided is a valid JSON stream. If you are absolutely sure it is valid JSON, you can use fromJust :: Maybe a -> a. But it is probably better to implement some sort of fallback mechanism.

Upvotes: 5

Related Questions