user1198582
user1198582

Reputation:

Is my ToJSON Base64 instance sane? What would FromJSON instance look like?

I'm trying to encode a Base64 to JSON. I came across this dialogue. I noticed snoyberg has already included the Base64 newtype in the FP Complete codebase, so I thought I would give that a try.

import qualified Data.ByteString as B

import qualified Data.ByteString.Base64 as B64

newtype Base64 = Base64 { toByteString :: B.ByteString }
deriving ( Eq, Ord, Show,IsString)

The ToJSON instance seemed simple enough. I'd appreciate a sanity check.

instance ToJSON Base64 where toJSON (Base64 bs) =
toJSON $ B.unpack $ B64.decodeLenient bs

The FromJSON instance is where I run into problems.

Studying other examples I surmise I must use withArray which wants a (Array -> Parser a). This is where I get stuck.

parseBase64 :: Array -> Parser

parseBase64 (Array a) = ...

I've tried many approaches here, I'm confused as to what needs to happen here or even if I'm on the right track at all. If I could get some feedback as simple as "you're on the right track keep going", or being pointed in a different direction, that would be appreciated.

Upvotes: 0

Views: 109

Answers (1)

phadej
phadej

Reputation: 12133

I have done this exercise before: https://github.com/futurice/haskell-base64-bytestring-type/blob/0a1176d16c71c219fe113bc3f130f64d8dda47bc/src/Data/ByteString/Base64/Type.hs#L46-L51

-- | Get base64 encoded bytestring
getEncodedByteString64 :: ByteString64 -> ByteString
getEncodedByteString64 = Base64.encode . getByteString64

instance ToJSON ByteString64 where
    toJSON = toJSON . decodeLatin1 . getEncodedByteString64

instance FromJSON ByteString64 where
    parseJSON = withText "ByteString" $
        pure . ByteString64 . decodeLenient . encodeUtf8

After closer look, seems that you encode ByteString (any) as an array of 'Word8', (this what BS.unpack returns) for that you could do, and have an encoded bytestring in Base64:

instance ToJSON Base64 where
    toJSON = toJSON . B.unpack . B64.decodeLenient . toByteString

instance FromJSON Base64 where
    parseJSON = fmap (Base64 . B64.encode . B.pack) . parseJSON

This way you don't have to worry how [Word8] is encoded, as far as it's decoded accordinly.

If you like to handle array manually, then it would look like:

instance FromJSON Base64 where
    parseJSON (Array a) = do
        a' <- traverse parseJSON a  -- :: Parser (V.Vector Word8)
        return $ Base64 . B64.encode . B.pack . V.toList $ a'
    parseJSON _ = fail "Array expected"

or

instance FromJSON Base64 where
    parseJSON = withArray "Base64" $ \a -> do
        a' <- traverse parseJSON a  -- :: Parser (V.Vector Word8)
        return $ Base64 . B64.encode . B.pack . V.toList $ a'

Upvotes: 1

Related Questions