packages feed

ideas-1.9.1: src/Ideas/Text/JSON/Decoder.hs

-----------------------------------------------------------------------------
-- Copyright 2019, Ideas project team. This file is distributed under the
-- terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-----------------------------------------------------------------------------

module Ideas.Text.JSON.Decoder
   ( GDecoderJSON, evalGDecoderJSON
   , DecoderJSON, evalDecoderJSON
   , Error, errorStr
   , jObject, jKey, jWithKeys, jObjectWithKeys
   , jArray, jArrayOf, jArray0, jArray1, jArray2, jArray3
   , jString, jChar, jBool, jInteger, jInt, jDouble, jFloat
   , jNull, jEmpty, jSkip
   , jNext
     -- re-exports
   , Alternative(..), MonadReader(..), throwError
   ) where

import Control.Monad
import Ideas.Text.JSON.Builder
import Ideas.Text.JSON.Data
import Ideas.Utils.Decoding

type GDecoderJSON env = Decoder env (Error JSON) JSONBuilder

evalGDecoderJSON :: GDecoderJSON env a -> env -> JSON -> Either (Error JSON) a
evalGDecoderJSON p env = evalDecoder p env . jsonToBuilder

type DecoderJSON = GDecoderJSON ()

evalDecoderJSON :: DecoderJSON a -> JSON -> Either (Error JSON) a
evalDecoderJSON p = evalGDecoderJSON p ()

jObject :: GDecoderJSON env a -> GDecoderJSON env a
jObject p = jFirst $ \a -> 
   case a of 
      Just (Object xs) -> put (mconcat $ map (uncurry tagJSON) xs) >> p
      _ -> raiseError "not an object" a

jKey :: Key -> GDecoderJSON env a -> GDecoderJSON env a
jKey k p = get2 >>= \(loc, xs) -> 
   case extractKey k xs of
      Just (v, rest) -> put2 (LocByKey 0 k loc, jsonToBuilder v) *> p <* put2 (loc, rest)
      _ -> raiseError ("key '" ++ k ++ "' not found") (Just (builderToJSON xs))

jWithKeys :: (Key -> GDecoderJSON env a) -> GDecoderJSON env [a]
jWithKeys f = get2 >>= \(loc, xs) ->
   mapM (\(k, v) -> put2 (LocByKey 0 k loc, jsonToBuilder v) >> f k) (extractKeyAndValues xs)

jObjectWithKeys :: (Key -> GDecoderJSON env a) -> GDecoderJSON env [a]
jObjectWithKeys = jObject . jWithKeys

jArray :: GDecoderJSON env a -> GDecoderJSON env a
jArray p = jFirst $ \a -> 
   case a of 
      Just (Array xs) -> changeLoc (LocByPos 0) >> put (mconcat $ map jsonToBuilder xs) >> p
      _ -> raiseError "not an array" a

jArrayOf :: GDecoderJSON env a -> GDecoderJSON env [a]
jArrayOf p = jArray $ many p <* jEmpty

jArray0 :: GDecoderJSON env ()
jArray0 = jArray jEmpty

jArray1 :: GDecoderJSON env a -> GDecoderJSON env a
jArray1 p = jArray $ p <* jEmpty

jArray2 :: (a -> b -> c) -> GDecoderJSON env a -> GDecoderJSON env b -> GDecoderJSON env c
jArray2 f p q = jArray $ f <$> p <*> q <* jEmpty

jArray3 :: (a -> b -> c -> d) -> GDecoderJSON env a -> GDecoderJSON env b -> GDecoderJSON env c -> GDecoderJSON env d
jArray3 f p q r = jArray $ f <$> p <*> q <*> r <* jEmpty

jString :: GDecoderJSON env String
jString = jFirst $ \a ->
   case a of
      Just (String s) -> return s
      _ -> raiseError "not a string" a

jChar :: GDecoderJSON env Char
jChar = jFirst $ \a ->
   case a of
      Just (String [c]) -> return c
      _ -> raiseError "not a char" a

jBool :: GDecoderJSON env Bool
jBool = jFirst $ \a ->
   case a of
      Just (Boolean b) -> return b
      _ -> raiseError "not a boolean" a

jInteger :: GDecoderJSON env Integer
jInteger = jFirst $ \a ->
   case a of
      Just (Integer i) -> return i
      _ -> raiseError "not an integer" a

jInt :: GDecoderJSON env Int
jInt = fromInteger <$> jInteger

jDouble :: GDecoderJSON env Double
jDouble = jFirst $ \a ->
   case a of
      Just (Double d) -> return d
      _ -> raiseError "not a double" a

jFloat :: GDecoderJSON env Float
jFloat = realToFrac <$> jDouble

jNull :: GDecoderJSON env ()
jNull = jFirst $ \a ->
   case a of
      Just Null -> return ()
      _ -> raiseError "not null" a

jEmpty :: GDecoderJSON env ()
jEmpty = get >>= \xs ->
   unless (isEmptyBuilder xs) (raiseError "not empty" (Just (builderToJSON xs)))

jSkip :: GDecoderJSON env ()
jSkip = jNext $ const $ return ()

jNext :: (JSON -> Either String a) -> GDecoderJSON env a
jNext f = jFirst $ \mjson -> 
   case fmap f mjson of
      Just res -> either errorStr return res
      Nothing  -> raiseError "no next element" Nothing

-- local helper: not exported
jFirst :: (Maybe JSON -> GDecoderJSON env a) -> GDecoderJSON env a
jFirst f = get >>= \xs ->
   case extractFirst xs of
      Just (json, rest) -> f (Just json) <* changeLoc nextLoc <* put rest
      _ -> f Nothing

get2 :: GDecoderJSON env (Loc, JSONBuilder)
get2 = (,) <$> getLoc <*> get

put2 :: (Loc, JSONBuilder) -> GDecoderJSON env ()
put2 (loc, xs) = putLoc loc >> put xs