scrod-0.2026.2.21: source/library/Scrod/Json/Value.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
module Scrod.Json.Value where
import qualified Data.ByteString.Builder as Builder
import qualified Data.Text as Text
import qualified Scrod.Decimal as Decimal
import qualified Scrod.Extra.Builder as Builder
import qualified Scrod.Extra.Parsec as Parsec
import qualified Scrod.Json.Array as Array
import qualified Scrod.Json.Boolean as Boolean
import qualified Scrod.Json.Null as Null
import qualified Scrod.Json.Number as Number
import qualified Scrod.Json.Object as Object
import qualified Scrod.Json.Pair as Pair
import qualified Scrod.Json.String as String
import qualified Scrod.Spec as Spec
import qualified Text.Parsec as Parsec
data Value
= Null Null.Null
| Boolean Boolean.Boolean
| Number Number.Number
| String String.String
| Array (Array.Array Value)
| Object (Object.Object Value)
deriving (Eq, Ord, Show)
null :: Value
null = Null Null.MkNull
optional :: (a -> Value) -> Maybe a -> Value
optional = maybe Scrod.Json.Value.null
boolean :: Bool -> Value
boolean = Boolean . Boolean.MkBoolean
number :: Integer -> Integer -> Value
number m = Number . Number.MkNumber . Decimal.mkDecimal m
integer :: Integer -> Value
integer = flip number 0
integral :: (Integral a) => a -> Value
integral = integer . toInteger
text :: Text.Text -> Value
text = String . String.MkString
string :: String -> Value
string = text . Text.pack
array :: [Value] -> Value
array = Array . Array.MkArray
arrayOf :: (a -> Value) -> [a] -> Value
arrayOf f = array . fmap f
pair :: String -> a -> Pair.Pair a
pair = Pair.MkPair . String.MkString . Text.pack
object :: [(String, Value)] -> Value
object = Object . Object.MkObject . fmap (uncurry pair)
tagged :: String -> Value -> Value
tagged t v = object [("type", string t), ("value", v)]
decode :: (Parsec.Stream s m Char) => Parsec.ParsecT s u m Value
decode =
Parsec.between (Parsec.many Parsec.blank) (Parsec.many Parsec.blank) $
Parsec.choice
[ Null <$> Null.decode,
Boolean <$> Boolean.decode,
Number <$> Number.decode,
String <$> String.decode,
Array <$> Array.decode decode,
Object <$> Object.decode decode
]
encode :: Value -> Builder.Builder
encode v = case v of
Null n -> Null.encode n
Boolean b -> Boolean.encode b
Number n -> Number.encode n
String s -> String.encode s
Array a -> Array.encode encode a
Object o -> Object.encode encode o
spec :: (Applicative m, Monad n) => Spec.Spec m n -> n ()
spec s = do
let null_ = Scrod.Json.Value.null
Spec.named s 'decode $ do
Spec.it s "parses null" $ do
Spec.assertEq s (Parsec.parseString decode "null") $ Just null_
Spec.it s "parses true" $ do
Spec.assertEq s (Parsec.parseString decode "true") . Just $ boolean True
Spec.it s "parses false" $ do
Spec.assertEq s (Parsec.parseString decode "false") . Just $ boolean False
Spec.it s "parses number" $ do
Spec.assertEq s (Parsec.parseString decode "123") . Just $ number 123 0
Spec.it s "parses string" $ do
Spec.assertEq s (Parsec.parseString decode "\"hello\"") . Just $ string "hello"
Spec.it s "parses empty array" $ do
Spec.assertEq s (Parsec.parseString decode "[]") . Just $ array []
Spec.it s "parses array with values" $ do
Spec.assertEq s (Parsec.parseString decode "[1, \"a\", true]") . Just $ array [number 1 0, string "a", boolean True]
Spec.it s "parses empty object" $ do
Spec.assertEq s (Parsec.parseString decode "{}") . Just $ object []
Spec.it s "parses object with values" $ do
Spec.assertEq s (Parsec.parseString decode "{\"a\": 1, \"b\": \"x\"}") . Just $ object [("a", number 1 0), ("b", string "x")]
Spec.it s "parses nested structure" $ do
Spec.assertEq s (Parsec.parseString decode "{\"items\": [1, 2], \"name\": \"test\"}") . Just $ object [("items", array [number 1 0, number 2 0]), ("name", string "test")]
Spec.it s "parses with leading whitespace" $ do
Spec.assertEq s (Parsec.parseString decode " null") $ Just null_
Spec.it s "parses with trailing whitespace" $ do
Spec.assertEq s (Parsec.parseString decode "null ") $ Just null_
Spec.it s "fails with invalid input" $ do
Spec.assertEq s (Parsec.parseString decode "invalid") Nothing
Spec.named s 'encode $ do
Spec.it s "encodes null" $ do
Spec.assertEq s (Builder.toString $ encode null_) "null"
Spec.it s "encodes true" $ do
Spec.assertEq s (Builder.toString . encode $ boolean True) "true"
Spec.it s "encodes false" $ do
Spec.assertEq s (Builder.toString . encode $ boolean False) "false"
Spec.it s "encodes number" $ do
Spec.assertEq s (Builder.toString . encode $ number 123 0) "123"
Spec.it s "encodes string" $ do
Spec.assertEq s (Builder.toString . encode $ string "hello") "\"hello\""
Spec.it s "encodes empty array" $ do
Spec.assertEq s (Builder.toString . encode $ array []) "[]"
Spec.it s "encodes array with values" $ do
Spec.assertEq s (Builder.toString . encode $ array [number 1 0, string "a"]) "[1,\"a\"]"
Spec.it s "encodes empty object" $ do
Spec.assertEq s (Builder.toString . encode $ object []) "{}"
Spec.it s "encodes object with values" $ do
Spec.assertEq s (Builder.toString . encode $ object [("a", number 1 0)]) "{\"a\":1}"
Spec.it s "encodes nested structure" $ do
Spec.assertEq s (Builder.toString . encode $ object [("items", array [number 1 0])]) "{\"items\":[1]}"