{-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE Trustworthy #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -------------------------------------------------------------------- -- | -- Copyright : (c) Alexey Raga 2016, (c) Edward Kmett 2013-2014, (c) Paul Wilson 2012 -- License : BSD3 -- Maintainer: Alexey Raga -- Stability : experimental -- Portability: non-portable -------------------------------------------------------------------- module HaskellWorks.Data.Json.Lens where import Control.Applicative import Control.Lens import Data.Data import Data.Scientific (Scientific) import GHC.Base import HaskellWorks.Data.Json.PartialValue as J import HaskellWorks.Data.ListMap (ListMap, fromList, toList) import Prelude hiding (null) import qualified Data.Scientific as Scientific ------------------------------------------------------------------------------ -- Scientific prisms ------------------------------------------------------------------------------ class AsNumber t where -- | -- >>> "[1, \"x\"]" ^? nth 0 . _Number -- Just 1.0 -- -- >>> "[1, \"x\"]" ^? nth 1 . _Number -- Nothing _Number :: Prism' t Scientific #ifndef HLINT default _Number :: AsPrimitive t => Prism' t Scientific _Number = _Primitive._Number {-# INLINE _Number #-} #endif -- | -- Prism into an 'Double' over a 'Value', 'Primitive' or 'Scientific' -- -- >>> "[10.2]" ^? nth 0 . _Double -- Just 10.2 _Double :: Prism' t Double _Double = _Number.iso Scientific.toRealFloat realToFrac {-# INLINE _Double #-} -- | -- Prism into an 'Integer' over a 'Value', 'Primitive' or 'Scientific' -- -- >>> "[10]" ^? nth 0 . _Integer -- Just 10 -- -- >>> "[10.5]" ^? nth 0 . _Integer -- Just 10 -- -- >>> "42" ^? _Integer -- Just 42 _Integer :: Prism' t Integer _Integer = _Number.iso floor fromIntegral {-# INLINE _Integer #-} instance AsNumber JsonPartialValue where _Number = prism (JsonPartialNumber . realToFrac) $ \v -> case v of JsonPartialNumber n -> Right (Scientific.fromFloatDigits n) _ -> Left v {-# INLINE _Number #-} instance AsNumber Scientific where _Number = id {-# INLINE _Number #-} -- We can implement these once jw-json declared FromJSON/ToJSON classes -- instance AsNumber Strict.ByteString -- instance AsNumber Lazy.ByteString -- instance AsNumber Text -- instance AsNumber LazyText.Text -- instance AsNumber String ------------------------------------------------------------------------------ -- Conversion Prisms ------------------------------------------------------------------------------ -- | Access Integer 'Value's as Integrals. -- -- >>> "[10]" ^? nth 0 . _Integral -- Just 10 -- -- >>> "[10.5]" ^? nth 0 . _Integral -- Just 10 _Integral :: (AsNumber t, Integral a) => Prism' t a _Integral = _Number . iso floor fromIntegral {-# INLINE _Integral #-} ------------------------------------------------------------------------------ -- Null values and primitives ------------------------------------------------------------------------------ -- | Primitives of 'Value' data Primitive = StringPrim !String | NumberPrim !Scientific | BoolPrim !Bool | NullPrim deriving (Eq,Ord,Show,Data,Typeable) instance AsNumber Primitive where _Number = prism NumberPrim $ \v -> case v of NumberPrim s -> Right s; _ -> Left v {-# INLINE _Number #-} class AsNumber t => AsPrimitive t where -- | -- >>> "[1, \"x\", null, true, false]" ^? nth 0 . _Primitive -- Just (NumberPrim 1.0) -- -- >>> "[1, \"x\", null, true, false]" ^? nth 1 . _Primitive -- Just (StringPrim "x") -- -- >>> "[1, \"x\", null, true, false]" ^? nth 2 . _Primitive -- Just NullPrim -- -- >>> "[1, \"x\", null, true, false]" ^? nth 3 . _Primitive -- Just (BoolPrim True) -- -- >>> "[1, \"x\", null, true, false]" ^? nth 4 . _Primitive -- Just (BoolPrim False) _Primitive :: Prism' t Primitive #ifndef HLINT default _Primitive :: AsValue t => Prism' t Primitive _Primitive = _Value._Primitive {-# INLINE _Primitive #-} #endif -- | -- >>> "{\"a\": \"xyz\", \"b\": true}" ^? key "a" . _String -- Just "xyz" -- -- >>> "{\"a\": \"xyz\", \"b\": true}" ^? key "b" . _String -- Nothing -- -- >>> _Object._Wrapped # [("key" :: Text, _String # "value")] :: String -- "{\"key\":\"value\"}" _String :: Prism' t String _String = _Primitive.prism StringPrim (\v -> case v of StringPrim s -> Right s; _ -> Left v) {-# INLINE _String #-} -- | -- >>> "{\"a\": \"xyz\", \"b\": true}" ^? key "b" . _Bool -- Just True -- -- >>> "{\"a\": \"xyz\", \"b\": true}" ^? key "a" . _Bool -- Nothing -- -- >>> _Bool # True :: String -- "true" -- -- >>> _Bool # False :: String -- "false" _Bool :: Prism' t Bool _Bool = _Primitive.prism BoolPrim (\v -> case v of BoolPrim b -> Right b; _ -> Left v) {-# INLINE _Bool #-} -- | -- >>> "{\"a\": \"xyz\", \"b\": null}" ^? key "b" . _Null -- Just () -- -- >>> "{\"a\": \"xyz\", \"b\": null}" ^? key "a" . _Null -- Nothing -- -- >>> _Null # () :: String -- "null" _Null :: Prism' t () _Null = _Primitive.prism (const NullPrim) (\v -> case v of NullPrim -> Right (); _ -> Left v) {-# INLINE _Null #-} instance AsPrimitive JsonPartialValue where _Primitive = prism fromPrim toPrim where toPrim (JsonPartialString s) = Right $ StringPrim s toPrim (JsonPartialNumber n) = Right $ NumberPrim (Scientific.fromFloatDigits n) toPrim (JsonPartialBool b) = Right $ BoolPrim b toPrim JsonPartialNull = Right NullPrim toPrim v = Left v {-# INLINE toPrim #-} fromPrim (StringPrim s) = JsonPartialString s fromPrim (NumberPrim n) = JsonPartialNumber (realToFrac n) fromPrim (BoolPrim b) = JsonPartialBool b fromPrim NullPrim = JsonPartialNull {-# INLINE fromPrim #-} {-# INLINE _Primitive #-} _String = prism JsonPartialString $ \v -> case v of JsonPartialString s -> Right s; _ -> Left v {-# INLINE _String #-} _Bool = prism JsonPartialBool (\v -> case v of JsonPartialBool b -> Right b; _ -> Left v) {-# INLINE _Bool #-} _Null = prism (const JsonPartialNull) (\v -> case v of JsonPartialNull -> Right (); _ -> Left v) {-# INLINE _Null #-} -- instance AsPrimitive Strict.ByteString -- instance AsPrimitive Lazy.ByteString -- instance AsPrimitive Text.Text -- instance AsPrimitive LazyText.Text -- instance AsPrimitive String instance AsPrimitive Primitive where _Primitive = id {-# INLINE _Primitive #-} -- | Prism into non-'Null' values -- -- >>> "{\"a\": \"xyz\", \"b\": null}" ^? key "a" . nonNull -- Just (String "xyz") -- -- >>> "{\"a\": {}, \"b\": null}" ^? key "a" . nonNull -- Just (Object (fromList [])) -- -- >>> "{\"a\": \"xyz\", \"b\": null}" ^? key "b" . nonNull -- Nothing nonNull :: Prism' JsonPartialValue JsonPartialValue nonNull = prism id (\v -> if isn't _Null v then Right v else Left v) {-# INLINE nonNull #-} ------------------------------------------------------------------------------ -- Non-primitive traversals ------------------------------------------------------------------------------ class AsPrimitive t => AsValue t where -- | -- >>> preview _Value "[1,2,3]" == Just (Array (Vector.fromList [Number 1.0,Number 2.0,Number 3.0])) -- True _Value :: Prism' t JsonPartialValue -- | -- >>> "{\"a\": {}, \"b\": null}" ^? key "a" . _Object -- Just (fromList []) -- -- >>> "{\"a\": {}, \"b\": null}" ^? key "b" . _Object -- Nothing -- -- >>> _Object._Wrapped # [("key" :: String, _String # "value")] :: String -- "{\"key\":\"value\"}" _Object :: Prism' t (ListMap JsonPartialValue) _Object = _Value.prism (JsonPartialObject . toList) (\v -> case v of JsonPartialObject o -> Right (fromList o) _ -> Left v) {-# INLINE _Object #-} -- | -- >>> preview _Array "[1,2,3]" == Just (Vector.fromList [Number 1.0,Number 2.0,Number 3.0]) -- True _Array :: Prism' t [JsonPartialValue] _Array = _Value.prism JsonPartialArray (\v -> case v of JsonPartialArray a -> Right a _ -> Left v) {-# INLINE _Array #-} instance AsValue JsonPartialValue where _Value = id {-# INLINE _Value #-} -- instance AsValue Strict.ByteString where -- _Value = _JSON -- {-# INLINE _Value #-} -- -- instance AsValue Lazy.ByteString where -- _Value = _JSON -- {-# INLINE _Value #-} -- -- instance AsValue String where -- _Value = strictUtf8._JSON -- {-# INLINE _Value #-} -- -- instance AsValue Text where -- _Value = strictTextUtf8._JSON -- {-# INLINE _Value #-} -- -- instance AsValue LazyText.Text where -- _Value = lazyTextUtf8._JSON -- {-# INLINE _Value #-} -- | -- Like 'ix', but for 'Object' with Text indices. This often has better -- inference than 'ix' when used with OverloadedStrings. -- -- >>> "{\"a\": 100, \"b\": 200}" ^? key "a" -- Just (Number 100.0) -- -- >>> "[1,2,3]" ^? key "a" -- Nothing key :: AsValue t => String -> Traversal' t JsonPartialValue key i = _Object . ix i {-# INLINE key #-} -- | An indexed Traversal into Object properties -- -- >>> "{\"a\": 4, \"b\": 7}" ^@.. members -- [("a",Number 4.0),("b",Number 7.0)] -- -- >>> "{\"a\": 4, \"b\": 7}" & members . _Number *~ 10 -- "{\"a\":40,\"b\":70}" members :: AsValue t => IndexedTraversal' String t JsonPartialValue members = _Object . itraversed {-# INLINE members #-} -- | Like 'ix', but for Arrays with Int indexes -- -- >>> "[1,2,3]" ^? nth 1 -- Just (Number 2.0) -- -- >>> "\"a\": 100, \"b\": 200}" ^? nth 1 -- Nothing -- -- >>> "[1,2,3]" & nth 1 .~ Number 20 -- "[1,20,3]" nth :: AsValue t => Int -> Traversal' t JsonPartialValue nth i = _Array . ix i {-# INLINE nth #-} -- | An indexed Traversal into Array elements -- -- >>> "[1,2,3]" ^.. values -- [Number 1.0,Number 2.0,Number 3.0] -- -- >>> "[1,2,3]" & values . _Number *~ 10 -- "[10,20,30]" values :: AsValue t => IndexedTraversal' Int t JsonPartialValue values = _Array . traversed {-# INLINE values #-} -- strictUtf8 :: Iso' String Strict.ByteString -- strictUtf8 = packed . strictTextUtf8 -- -- strictTextUtf8 :: Iso' Text.Text Strict.ByteString -- strictTextUtf8 = iso StrictText.encodeUtf8 StrictText.decodeUtf8 -- -- lazyTextUtf8 :: Iso' LazyText.Text Lazy.ByteString -- lazyTextUtf8 = iso LazyText.encodeUtf8 LazyText.decodeUtf8 -- class AsJSON t where -- -- | '_JSON' is a 'Prism' from something containing JSON to something encoded in that structure -- _JSON :: (FromJSON a, ToJSON a) => Prism' t a -- -- instance AsJSON Strict.ByteString where -- _JSON = lazy._JSON -- {-# INLINE _JSON #-} -- -- instance AsJSON Lazy.ByteString where -- _JSON = prism' encode decodeValue -- where -- decodeValue :: (FromJSON a) => Lazy.ByteString -> Maybe a -- decodeValue s = maybeResult (parse value s) >>= \x -> case fromJSON x of -- Success v -> Just v -- _ -> Nothing -- {-# INLINE _JSON #-} -- -- instance AsJSON String where -- _JSON = strictUtf8._JSON -- {-# INLINE _JSON #-} -- -- instance AsJSON Text where -- _JSON = strictTextUtf8._JSON -- {-# INLINE _JSON #-} -- -- instance AsJSON LazyText.Text where -- _JSON = lazyTextUtf8._JSON -- {-# INLINE _JSON #-} -- -- instance AsJSON Value where -- _JSON = prism toJSON $ \x -> case fromJSON x of -- Success y -> Right y; -- _ -> Left x -- {-# INLINE _JSON #-} ------------------------------------------------------------------------------ -- Some additional tests for prismhood; see https://github.com/ekmett/lens/issues/439. ------------------------------------------------------------------------------ -- $LazyByteStringTests -- >>> "42" ^? (_JSON :: Prism' Lazy.ByteString Value) -- Just (Number 42.0) -- -- >>> preview (_Integer :: Prism' Lazy.ByteString Integer) "42" -- Just 42 -- -- >>> Lazy.unpack (review (_Integer :: Prism' Lazy.ByteString Integer) 42) -- "42" -- $StrictByteStringTests -- >>> "42" ^? (_JSON :: Prism' Strict.ByteString Value) -- Just (Number 42.0) -- -- >>> preview (_Integer :: Prism' Strict.ByteString Integer) "42" -- Just 42 -- -- >>> Strict.Char8.unpack (review (_Integer :: Prism' Strict.ByteString Integer) 42) -- "42" -- $StringTests -- >>> "42" ^? (_JSON :: Prism' String Value) -- Just (Number 42.0) -- -- >>> preview (_Integer :: Prism' String Integer) "42" -- Just 42 -- -- >>> review (_Integer :: Prism' String Integer) 42 -- "42" ------------------------------------------------------------------------------ -- Orphan instances for lens library interop ------------------------------------------------------------------------------ type instance Index JsonPartialValue = String type instance IxValue JsonPartialValue = JsonPartialValue instance Ixed JsonPartialValue where ix i f (JsonPartialObject o) = (JsonPartialObject . toList) <$> ix i f (fromList o) ix _ _ v = pure v {-# INLINE ix #-} instance Plated JsonPartialValue where plate f (JsonPartialObject o) = (JsonPartialObject . toList) <$> traverse f (fromList o) plate f (JsonPartialArray a) = JsonPartialArray <$> traverse f a plate _ xs = pure xs {-# INLINE plate #-}