{-# OPTIONS -fno-warn-redundant-constraints #-} {-# LANGUAGE EmptyDataDecls #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RebindableSyntax #-} module SimpleJSON ( Value, Parser, toParser, rawParser, maybeParser, fromMaybeParser, listParser, (<<<), Rule, rawRule, rule, listRule, runParser, runListParser, withDecoder, withEncoder, decode, encode, decodeRaw, encodeRaw, ) where import Data.Text (Text, fromString) import Fay.Unsafe (unsafePerformFay) import FFI (ffi) import Prelude import Unsafe.Coerce (unsafeCoerce) data Value newtype Parser = Parser (Value -> Fay Value) data Rule = Rule Parser Text Text rule :: Parser -> Text -> Text -> Rule rule = Rule rawRule :: Text -> Text -> Rule rawRule = rule rawParser listRule :: Parser -> Text -> Text -> Rule listRule p = rule (listParser p) runRule :: Rule -> Value -> Value -> Fay Value runRule (Rule p ref key) v0 v1 = set v0 ref =<< runParser p =<< get v1 key toParser :: (Value -> Fay Value) -> Parser toParser = Parser rawParser :: Parser rawParser = toParser return (<<<) :: (Parser -> Parser) ->Parser -> Parser f <<< p = f p infixr 1 <<< isNull :: Value -> Bool isNull = ffi "(function(v) {\ \ if (!v) {\ \ return true;\ \ }\ \ if (Array.isArray(v)) {\ \ return v.length === 0;\ \ } else if (typeof v === 'object') {\ \ for (var i in v) {\ \ return false;\ \ }\ \ return true;\ \ }\ \ return false;\ \})(%1)" maybeParser :: Parser -> Parser maybeParser p = toParser toMaybe where toMaybe :: Value -> Fay Value toMaybe v | isNull v = nothing | otherwise = just v where nothing :: Fay Value nothing = newValue' "Nothing" just :: Value -> Fay Value just v' = do o <- newValue' "Just" set o "slot1" =<< runParser p v' nullValue :: Value nullValue = ffi "undefined" fromMaybeParser :: Parser -> Parser fromMaybeParser p = toParser $ \v -> do ins <- get v "_instance" if ins == "Just" then runParser p =<< get v "slot1" else return nullValue listParser :: Parser -> Parser listParser p = toParser $ runListParser p runParser :: Parser -> Value -> Fay Value runParser (Parser f) v = f v runListParser :: Parser -> Value -> Fay Value runListParser p v = do parsed <- mapM (runParser p) =<< toList v return $ unsafeCoerce parsed toList :: Value -> Fay [Value] toList = ffi "(function(v){ if (Array.isArray(v)){ return v; } else if (v) { return [v]; } else { return [] } })(%1)" set :: Value -> Text -> b -> Fay Value set = ffi "(function(obj, key, val) { obj[key] = val; return obj; })(%1, %2, %3)" get :: Value -> Text -> Fay b get = ffi "(function(v, k) { if (v) {return v[k]; } else { return undefined }})(%1, %2)" isList :: Value -> Bool isList = ffi "Array.isArray(%1)" decodeRaw :: Text -> Fay Value decodeRaw = ffi "JSON.parse(%1)" encodeRaw :: Value -> Fay Text encodeRaw = ffi "JSON.stringify(%1)" decode :: Text -> Parser -> a decode txt p = unsafePerformFay $ fixedInstance =<< runP p v where v = unsafePerformFay $ decodeRaw txt runP = if isList v then runListParser else runParser fixedInstance :: Value -> Fay a fixedInstance = ffi "(function(v){\ \ function fixedObject(v) {\ \ var o = {};\ \ for (var k in v) {\ \ if (k === '_instance') {\ \ o[k.substr(1)] = v[k];\ \ continue;\ \ }\ \ if (k === 'instance') {\ \ o['_' + k] = v[k];\ \ continue;\ \ }\ \ o[k] = fixed(v[k]);\ \ }\ \ return o;\ \ }\ \ function fixedArray(v) {\ \ return v.map(fixed);\ \ }\ \ function fixed(v) {\ \ if (Array.isArray(v)) {\ \ return fixedArray(v);\ \ } else if (typeof v === 'object') {\ \ return fixedObject(v);\ \ } else {\ \ return v\ \ }\ \ }\ \ return fixed(v);\ \})(%1)" encode :: a -> Parser -> Text encode obj p = unsafePerformFay (encodeRaw =<< runP p =<< fixedInstance v) where v = unsafeCoerce obj :: Value runP = if isList v then runListParser else runParser newValue :: Fay Value newValue = ffi "{}" newValue' :: Text -> Fay Value newValue' = ffi "{ _instance: %1 }" doParser :: Fay Value -> [Rule] -> Value -> Fay Value doParser obj rules ref = go ref rules =<< obj where go :: Value -> [Rule] -> Value -> Fay Value go v (x:xs) o = go v xs =<< runRule x o v go _ [] o = return o withDecoder :: Text -> [Rule] -> Parser withDecoder ins rules = toParser (doParser (newValue' ins) rules) withEncoder :: [Rule] -> Parser withEncoder rules = toParser (doParser newValue rules)