From 2b16a32be2779f7aa42b7081ce4c855c6e4322ee Mon Sep 17 00:00:00 2001 From: rightfold Date: Mon, 5 Jun 2017 13:03:20 +0200 Subject: [PATCH 1/3] Add generics-rep encoding and decoding --- README.md | 2 +- bower.json | 8 ++- src/Data/Argonaut/Decode/Generic.purs | 86 +++++++++++++++++++++++++-- src/Data/Argonaut/Encode/Generic.purs | 63 +++++++++++++++++++- test/Main.purs | 47 +++++++++++++++ 5 files changed, 195 insertions(+), 11 deletions(-) create mode 100644 test/Main.purs diff --git a/README.md b/README.md index 9744dad..3d6fbfd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Build status](https://travis-ci.org/purescript-contrib/purescript-argonaut-generic.svg?branch=master)](https://travis-ci.org/purescript-contrib/purescript-argonaut-generic) [![Maintainer: slamdata](https://img.shields.io/badge/maintainer-slamdata-lightgrey.svg)](http://github.com/slamdata) -This package provides `gEncodeJson` and `gDecodeJson` functions for any data types that have a `Generic` instance. Currently only [`purescript-generics`](https://github.com/purescript/purescript-generics) is supported (not [`purescript-generics-rep`](https://github.com/purescript/purescript-generics-rep)). +This package provides `gEncodeJson` and `gDecodeJson` functions for any data types that have a `Generic` instance, and `genericEncodeJson` and `genericDecodeJson` functions for any data types that have a `Rep.Generic` instance. ## Installation diff --git a/bower.json b/bower.json index b3d85e5..21ac983 100644 --- a/bower.json +++ b/bower.json @@ -18,7 +18,11 @@ "dependencies": { "purescript-generics": "^4.0.0", "purescript-argonaut-core": "^3.1.0", - "purescript-argonaut-codecs": "^3.0.0" + "purescript-argonaut-codecs": "^3.0.0", + "purescript-generics-rep": "^5.1.0" }, - "devDependencies": {} + "devDependencies": { + "purescript-assert": "^3.0.0", + "purescript-console": "^3.0.0" + } } diff --git a/src/Data/Argonaut/Decode/Generic.purs b/src/Data/Argonaut/Decode/Generic.purs index 5addf86..833e899 100644 --- a/src/Data/Argonaut/Decode/Generic.purs +++ b/src/Data/Argonaut/Decode/Generic.purs @@ -1,19 +1,32 @@ module Data.Argonaut.Decode.Generic ( gDecodeJson, - gDecodeJson' + gDecodeJson', + + class DecodeRep, + class DecodeRepArgs, + class DecodeRepFields, + decodeRep, + decodeRepArgs, + decodeRepFields, + genericDecodeJson ) where import Prelude +import Control.Alt ((<|>)) import Data.Argonaut.Core (Json, toArray, toBoolean, toNumber, toObject, toString) -import Data.Array (zipWithA) +import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson) +import Data.Array (uncons, zipWithA) +import Data.Bifunctor (lmap) import Data.Either (Either(..)) import Data.Foldable (find) import Data.Generic (class Generic, GenericSpine(..), GenericSignature(..), fromSpine, toSignature) +import Data.Generic.Rep as Rep import Data.Int (fromNumber) import Data.Maybe (maybe, Maybe(..)) import Data.String (toChar) import Data.StrMap as SM +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) import Data.Traversable (traverse, for) import Type.Proxy (Proxy(..)) @@ -53,6 +66,69 @@ gDecodeJson' signature json = case signature of vals <- mFail (decodingErr "'values' array is missing") (toArray =<< SM.lookup "values" jObj) sps <- zipWithA (\k -> gDecodeJson' (k unit)) sigValues vals pure (SProd tag (const <$> sps)) - where - mFail :: forall a. String -> Maybe a -> Either String a - mFail msg = maybe (Left msg) Right + +class DecodeRep r where + decodeRep :: Json -> Either String r + +instance decodeRepNoConstructors :: DecodeRep Rep.NoConstructors where + decodeRep _ = Left "Cannot decode empty data type" + +instance decodeRepSum :: (DecodeRep a, DecodeRep b) => DecodeRep (Rep.Sum a b) where + decodeRep j = Rep.Inl <$> decodeRep j <|> Rep.Inr <$> decodeRep j + +instance decodeRepConstructor :: (IsSymbol name, DecodeRepArgs a) => DecodeRep (Rep.Constructor name a) where + decodeRep j = do + let name = reflectSymbol (SProxy :: SProxy name) + let decodingErr msg = "When decoding a " <> name <> ": " <> msg + jObj <- mFail (decodingErr "expected an object") (toObject j) + jTag <- mFail (decodingErr "'tag' property is missing") (SM.lookup "tag" jObj) + tag <- mFail (decodingErr "'tag' property is not a string") (toString jTag) + when (tag /= name) $ + Left $ decodingErr "'tag' property has an incorrect value" + jValues <- mFail (decodingErr "'values' property is missing") (SM.lookup "values" jObj) + values <- mFail (decodingErr "'values' property is not an array") (toArray jValues) + {init, rest} <- lmap decodingErr $ decodeRepArgs values + when (rest /= []) $ + Left $ decodingErr "'values' property had too many values" + pure $ Rep.Constructor init + +class DecodeRepArgs r where + decodeRepArgs :: Array Json -> Either String {init :: r, rest :: Array Json} + +instance decodeRepArgsNoArguments :: DecodeRepArgs Rep.NoArguments where + decodeRepArgs js = Right {init: Rep.NoArguments, rest: js} + +instance decodeRepArgsProduct :: (DecodeRepArgs a, DecodeRepArgs b) => DecodeRepArgs (Rep.Product a b) where + decodeRepArgs js = do + {init: a, rest: js'} <- decodeRepArgs js + {init: b, rest: js''} <- decodeRepArgs js' + pure {init: Rep.Product a b, rest: js''} + +instance decodeRepArgsArgument :: (DecodeJson a) => DecodeRepArgs (Rep.Argument a) where + decodeRepArgs js = do + {head, tail} <- mFail "too few values were present" (uncons js) + {init: _, rest: tail} <<< Rep.Argument <$> decodeJson head + +instance decodeRepArgsRec :: (DecodeRepFields fields) => DecodeRepArgs (Rep.Rec fields) where + decodeRepArgs js = do + {head, tail} <- mFail "too few values were present" (uncons js) + jObj <- mFail "record is not encoded as an object" (toObject head) + {init: _, rest: tail} <<< Rep.Rec <$> decodeRepFields jObj + +class DecodeRepFields r where + decodeRepFields :: SM.StrMap Json -> Either String r + +instance decodeRepFieldsProduct :: (DecodeRepFields a, DecodeRepFields b) => DecodeRepFields (Rep.Product a b) where + decodeRepFields js = Rep.Product <$> decodeRepFields js <*> decodeRepFields js + +instance decodeRepFieldsField :: (IsSymbol field, DecodeJson a) => DecodeRepFields (Rep.Field field a) where + decodeRepFields js = do + let name = reflectSymbol (SProxy :: SProxy field) + value <- mFail ("the field '" <> name <> "' is not present") (SM.lookup name js) + Rep.Field <$> decodeJson value + +genericDecodeJson :: forall a r. Rep.Generic a r => DecodeRep r => Json -> Either String a +genericDecodeJson = map Rep.to <<< decodeRep + +mFail :: forall a. String -> Maybe a -> Either String a +mFail msg = maybe (Left msg) Right diff --git a/src/Data/Argonaut/Encode/Generic.purs b/src/Data/Argonaut/Encode/Generic.purs index 8744eba..4d317af 100644 --- a/src/Data/Argonaut/Encode/Generic.purs +++ b/src/Data/Argonaut/Encode/Generic.purs @@ -1,17 +1,27 @@ module Data.Argonaut.Encode.Generic ( - gEncodeJson, - gEncodeJson' + gEncodeJson, + gEncodeJson', + + class EncodeRep, + class EncodeRepArgs, + class EncodeRepFields, + encodeRep, + encodeRepArgs, + encodeRepFields, + genericEncodeJson ) where import Prelude -import Data.Argonaut.Encode.Class (encodeJson) +import Data.Argonaut.Encode.Class (class EncodeJson, encodeJson) import Data.Argonaut.Core (Json(), jsonNull, fromBoolean, fromNumber, fromString, fromArray, fromObject) import Data.Int (toNumber) import Data.Foldable (foldr) import Data.Generic (class Generic, GenericSpine(..), toSpine) +import Data.Generic.Rep as Rep import Data.String (singleton) import Data.StrMap as SM +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) -- | Encode any `Generic` data structure into `Json`. gEncodeJson :: forall a. Generic a => a -> Json @@ -36,3 +46,50 @@ gEncodeJson' = case _ of where addField field = SM.insert field.recLabel (gEncodeJson' $ field.recValue unit) + +class EncodeRep r where + encodeRep :: r -> Json + +instance encodeRepNoConstructors :: EncodeRep Rep.NoConstructors where + encodeRep r = encodeRep r + +instance encodeRepSum :: (EncodeRep a, EncodeRep b) => EncodeRep (Rep.Sum a b) where + encodeRep (Rep.Inl a) = encodeRep a + encodeRep (Rep.Inr b) = encodeRep b + +instance encodeRepConstructor :: (IsSymbol name, EncodeRepArgs a) => EncodeRep (Rep.Constructor name a) where + encodeRep (Rep.Constructor a) = + fromObject + $ SM.insert "tag" (fromString (reflectSymbol (SProxy :: SProxy name))) + $ SM.insert "values" (fromArray (encodeRepArgs a)) + $ SM.empty + +class EncodeRepArgs r where + encodeRepArgs :: r -> Array Json + +instance encodeRepArgsNoArguments :: EncodeRepArgs Rep.NoArguments where + encodeRepArgs Rep.NoArguments = [] + +instance encodeRepArgsProduct :: (EncodeRepArgs a, EncodeRepArgs b) => EncodeRepArgs (Rep.Product a b) where + encodeRepArgs (Rep.Product a b) = encodeRepArgs a <> encodeRepArgs b + +instance encodeRepArgsArgument :: (EncodeJson a) => EncodeRepArgs (Rep.Argument a) where + encodeRepArgs (Rep.Argument a) = [encodeJson a] + +instance encodeRepArgsRec :: (EncodeRepFields fields) => EncodeRepArgs (Rep.Rec fields) where + encodeRepArgs (Rep.Rec fields) = [fromObject $ encodeRepFields fields] + +class EncodeRepFields r where + encodeRepFields :: r -> SM.StrMap Json + +instance encodeRepFieldsProduct :: (EncodeRepFields a, EncodeRepFields b) => EncodeRepFields (Rep.Product a b) where + encodeRepFields (Rep.Product a b) = + SM.union (encodeRepFields a) (encodeRepFields b) + +instance encodeRepFieldsField :: (IsSymbol field, EncodeJson a) => EncodeRepFields (Rep.Field field a) where + encodeRepFields (Rep.Field a) = + SM.singleton (reflectSymbol (SProxy :: SProxy field)) + (encodeJson a) + +genericEncodeJson :: forall a r. Rep.Generic a r => EncodeRep r => a -> Json +genericEncodeJson = encodeRep <<< Rep.from diff --git a/test/Main.purs b/test/Main.purs new file mode 100644 index 0000000..45884b7 --- /dev/null +++ b/test/Main.purs @@ -0,0 +1,47 @@ +module Test.Main + ( main + ) where + +import Prelude + +import Control.Monad.Eff (Eff) +import Control.Monad.Eff.Console (CONSOLE, log) +import Data.Argonaut.Core (stringify) +import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson) +import Data.Argonaut.Decode.Generic (genericDecodeJson) +import Data.Argonaut.Encode.Class (class EncodeJson, encodeJson) +import Data.Argonaut.Encode.Generic (genericEncodeJson) +import Data.Either (Either(..)) +import Data.Generic.Rep (class Generic) +import Data.Generic.Rep.Show (genericShow) +import Test.Assert (ASSERT, assert) + +data Example + = Either (Either String Example) + | Record {foo :: Int, bar :: String} + | Product Int Int Example + +derive instance eqExample :: Eq Example +derive instance genericExample :: Generic Example _ +instance showExample :: Show Example where + show a = genericShow a +instance encodeJsonExample :: EncodeJson Example where + encodeJson a = genericEncodeJson a +instance decodeJson :: DecodeJson Example where + decodeJson a = genericDecodeJson a + +main :: forall eff. Eff (assert :: ASSERT, console :: CONSOLE | eff) Unit +main = do + example $ Either $ Left "foo" + example $ Either $ Right $ Either $ Left "foo" + example $ Record {foo: 42, bar: "bar"} + example $ Product 1 2 $ Either $ Left "foo" + where + example original = do + let json = encodeJson original + let parsed = decodeJson json + log $ "Original: " <> show original + log $ "To JSON: " <> stringify json + log $ "From JSON: " <> show parsed + assert $ parsed == Right original + log $ "--------------------------------------------------------------------------------" From 7550a6bf53b5857d3cfb61c021eb822cf9445571 Mon Sep 17 00:00:00 2001 From: rightfold Date: Mon, 5 Jun 2017 14:08:08 +0200 Subject: [PATCH 2/3] Move generics-rep code to separate module --- src/Data/Argonaut/Decode/Generic.purs | 86 ++-------------------- src/Data/Argonaut/Decode/Generic/Rep.purs | 88 +++++++++++++++++++++++ src/Data/Argonaut/Encode/Generic.purs | 61 +--------------- src/Data/Argonaut/Encode/Generic/Rep.purs | 64 +++++++++++++++++ test/Main.purs | 4 +- 5 files changed, 161 insertions(+), 142 deletions(-) create mode 100644 src/Data/Argonaut/Decode/Generic/Rep.purs create mode 100644 src/Data/Argonaut/Encode/Generic/Rep.purs diff --git a/src/Data/Argonaut/Decode/Generic.purs b/src/Data/Argonaut/Decode/Generic.purs index 833e899..5addf86 100644 --- a/src/Data/Argonaut/Decode/Generic.purs +++ b/src/Data/Argonaut/Decode/Generic.purs @@ -1,32 +1,19 @@ module Data.Argonaut.Decode.Generic ( gDecodeJson, - gDecodeJson', - - class DecodeRep, - class DecodeRepArgs, - class DecodeRepFields, - decodeRep, - decodeRepArgs, - decodeRepFields, - genericDecodeJson + gDecodeJson' ) where import Prelude -import Control.Alt ((<|>)) import Data.Argonaut.Core (Json, toArray, toBoolean, toNumber, toObject, toString) -import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson) -import Data.Array (uncons, zipWithA) -import Data.Bifunctor (lmap) +import Data.Array (zipWithA) import Data.Either (Either(..)) import Data.Foldable (find) import Data.Generic (class Generic, GenericSpine(..), GenericSignature(..), fromSpine, toSignature) -import Data.Generic.Rep as Rep import Data.Int (fromNumber) import Data.Maybe (maybe, Maybe(..)) import Data.String (toChar) import Data.StrMap as SM -import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) import Data.Traversable (traverse, for) import Type.Proxy (Proxy(..)) @@ -66,69 +53,6 @@ gDecodeJson' signature json = case signature of vals <- mFail (decodingErr "'values' array is missing") (toArray =<< SM.lookup "values" jObj) sps <- zipWithA (\k -> gDecodeJson' (k unit)) sigValues vals pure (SProd tag (const <$> sps)) - -class DecodeRep r where - decodeRep :: Json -> Either String r - -instance decodeRepNoConstructors :: DecodeRep Rep.NoConstructors where - decodeRep _ = Left "Cannot decode empty data type" - -instance decodeRepSum :: (DecodeRep a, DecodeRep b) => DecodeRep (Rep.Sum a b) where - decodeRep j = Rep.Inl <$> decodeRep j <|> Rep.Inr <$> decodeRep j - -instance decodeRepConstructor :: (IsSymbol name, DecodeRepArgs a) => DecodeRep (Rep.Constructor name a) where - decodeRep j = do - let name = reflectSymbol (SProxy :: SProxy name) - let decodingErr msg = "When decoding a " <> name <> ": " <> msg - jObj <- mFail (decodingErr "expected an object") (toObject j) - jTag <- mFail (decodingErr "'tag' property is missing") (SM.lookup "tag" jObj) - tag <- mFail (decodingErr "'tag' property is not a string") (toString jTag) - when (tag /= name) $ - Left $ decodingErr "'tag' property has an incorrect value" - jValues <- mFail (decodingErr "'values' property is missing") (SM.lookup "values" jObj) - values <- mFail (decodingErr "'values' property is not an array") (toArray jValues) - {init, rest} <- lmap decodingErr $ decodeRepArgs values - when (rest /= []) $ - Left $ decodingErr "'values' property had too many values" - pure $ Rep.Constructor init - -class DecodeRepArgs r where - decodeRepArgs :: Array Json -> Either String {init :: r, rest :: Array Json} - -instance decodeRepArgsNoArguments :: DecodeRepArgs Rep.NoArguments where - decodeRepArgs js = Right {init: Rep.NoArguments, rest: js} - -instance decodeRepArgsProduct :: (DecodeRepArgs a, DecodeRepArgs b) => DecodeRepArgs (Rep.Product a b) where - decodeRepArgs js = do - {init: a, rest: js'} <- decodeRepArgs js - {init: b, rest: js''} <- decodeRepArgs js' - pure {init: Rep.Product a b, rest: js''} - -instance decodeRepArgsArgument :: (DecodeJson a) => DecodeRepArgs (Rep.Argument a) where - decodeRepArgs js = do - {head, tail} <- mFail "too few values were present" (uncons js) - {init: _, rest: tail} <<< Rep.Argument <$> decodeJson head - -instance decodeRepArgsRec :: (DecodeRepFields fields) => DecodeRepArgs (Rep.Rec fields) where - decodeRepArgs js = do - {head, tail} <- mFail "too few values were present" (uncons js) - jObj <- mFail "record is not encoded as an object" (toObject head) - {init: _, rest: tail} <<< Rep.Rec <$> decodeRepFields jObj - -class DecodeRepFields r where - decodeRepFields :: SM.StrMap Json -> Either String r - -instance decodeRepFieldsProduct :: (DecodeRepFields a, DecodeRepFields b) => DecodeRepFields (Rep.Product a b) where - decodeRepFields js = Rep.Product <$> decodeRepFields js <*> decodeRepFields js - -instance decodeRepFieldsField :: (IsSymbol field, DecodeJson a) => DecodeRepFields (Rep.Field field a) where - decodeRepFields js = do - let name = reflectSymbol (SProxy :: SProxy field) - value <- mFail ("the field '" <> name <> "' is not present") (SM.lookup name js) - Rep.Field <$> decodeJson value - -genericDecodeJson :: forall a r. Rep.Generic a r => DecodeRep r => Json -> Either String a -genericDecodeJson = map Rep.to <<< decodeRep - -mFail :: forall a. String -> Maybe a -> Either String a -mFail msg = maybe (Left msg) Right + where + mFail :: forall a. String -> Maybe a -> Either String a + mFail msg = maybe (Left msg) Right diff --git a/src/Data/Argonaut/Decode/Generic/Rep.purs b/src/Data/Argonaut/Decode/Generic/Rep.purs new file mode 100644 index 0000000..a713b7f --- /dev/null +++ b/src/Data/Argonaut/Decode/Generic/Rep.purs @@ -0,0 +1,88 @@ +module Data.Argonaut.Decode.Generic.Rep ( + class DecodeRep, + class DecodeRepArgs, + class DecodeRepFields, + decodeRep, + decodeRepArgs, + decodeRepFields, + genericDecodeJson +) where + +import Prelude + +import Control.Alt ((<|>)) +import Data.Argonaut.Core (Json, toArray, toObject, toString) +import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson) +import Data.Array (uncons) +import Data.Bifunctor (lmap) +import Data.Either (Either(..)) +import Data.Generic.Rep as Rep +import Data.Maybe (Maybe, maybe) +import Data.StrMap as SM +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) + +class DecodeRep r where + decodeRep :: Json -> Either String r + +instance decodeRepNoConstructors :: DecodeRep Rep.NoConstructors where + decodeRep _ = Left "Cannot decode empty data type" + +instance decodeRepSum :: (DecodeRep a, DecodeRep b) => DecodeRep (Rep.Sum a b) where + decodeRep j = Rep.Inl <$> decodeRep j <|> Rep.Inr <$> decodeRep j + +instance decodeRepConstructor :: (IsSymbol name, DecodeRepArgs a) => DecodeRep (Rep.Constructor name a) where + decodeRep j = do + let name = reflectSymbol (SProxy :: SProxy name) + let decodingErr msg = "When decoding a " <> name <> ": " <> msg + jObj <- mFail (decodingErr "expected an object") (toObject j) + jTag <- mFail (decodingErr "'tag' property is missing") (SM.lookup "tag" jObj) + tag <- mFail (decodingErr "'tag' property is not a string") (toString jTag) + when (tag /= name) $ + Left $ decodingErr "'tag' property has an incorrect value" + jValues <- mFail (decodingErr "'values' property is missing") (SM.lookup "values" jObj) + values <- mFail (decodingErr "'values' property is not an array") (toArray jValues) + {init, rest} <- lmap decodingErr $ decodeRepArgs values + when (rest /= []) $ + Left $ decodingErr "'values' property had too many values" + pure $ Rep.Constructor init + +class DecodeRepArgs r where + decodeRepArgs :: Array Json -> Either String {init :: r, rest :: Array Json} + +instance decodeRepArgsNoArguments :: DecodeRepArgs Rep.NoArguments where + decodeRepArgs js = Right {init: Rep.NoArguments, rest: js} + +instance decodeRepArgsProduct :: (DecodeRepArgs a, DecodeRepArgs b) => DecodeRepArgs (Rep.Product a b) where + decodeRepArgs js = do + {init: a, rest: js'} <- decodeRepArgs js + {init: b, rest: js''} <- decodeRepArgs js' + pure {init: Rep.Product a b, rest: js''} + +instance decodeRepArgsArgument :: (DecodeJson a) => DecodeRepArgs (Rep.Argument a) where + decodeRepArgs js = do + {head, tail} <- mFail "too few values were present" (uncons js) + {init: _, rest: tail} <<< Rep.Argument <$> decodeJson head + +instance decodeRepArgsRec :: (DecodeRepFields fields) => DecodeRepArgs (Rep.Rec fields) where + decodeRepArgs js = do + {head, tail} <- mFail "too few values were present" (uncons js) + jObj <- mFail "record is not encoded as an object" (toObject head) + {init: _, rest: tail} <<< Rep.Rec <$> decodeRepFields jObj + +class DecodeRepFields r where + decodeRepFields :: SM.StrMap Json -> Either String r + +instance decodeRepFieldsProduct :: (DecodeRepFields a, DecodeRepFields b) => DecodeRepFields (Rep.Product a b) where + decodeRepFields js = Rep.Product <$> decodeRepFields js <*> decodeRepFields js + +instance decodeRepFieldsField :: (IsSymbol field, DecodeJson a) => DecodeRepFields (Rep.Field field a) where + decodeRepFields js = do + let name = reflectSymbol (SProxy :: SProxy field) + value <- mFail ("the field '" <> name <> "' is not present") (SM.lookup name js) + Rep.Field <$> decodeJson value + +genericDecodeJson :: forall a r. Rep.Generic a r => DecodeRep r => Json -> Either String a +genericDecodeJson = map Rep.to <<< decodeRep + +mFail :: forall a. String -> Maybe a -> Either String a +mFail msg = maybe (Left msg) Right diff --git a/src/Data/Argonaut/Encode/Generic.purs b/src/Data/Argonaut/Encode/Generic.purs index 4d317af..6a6db07 100644 --- a/src/Data/Argonaut/Encode/Generic.purs +++ b/src/Data/Argonaut/Encode/Generic.purs @@ -1,27 +1,17 @@ module Data.Argonaut.Encode.Generic ( gEncodeJson, - gEncodeJson', - - class EncodeRep, - class EncodeRepArgs, - class EncodeRepFields, - encodeRep, - encodeRepArgs, - encodeRepFields, - genericEncodeJson + gEncodeJson' ) where import Prelude -import Data.Argonaut.Encode.Class (class EncodeJson, encodeJson) +import Data.Argonaut.Encode.Class (encodeJson) import Data.Argonaut.Core (Json(), jsonNull, fromBoolean, fromNumber, fromString, fromArray, fromObject) import Data.Int (toNumber) import Data.Foldable (foldr) import Data.Generic (class Generic, GenericSpine(..), toSpine) -import Data.Generic.Rep as Rep import Data.String (singleton) import Data.StrMap as SM -import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) -- | Encode any `Generic` data structure into `Json`. gEncodeJson :: forall a. Generic a => a -> Json @@ -46,50 +36,3 @@ gEncodeJson' = case _ of where addField field = SM.insert field.recLabel (gEncodeJson' $ field.recValue unit) - -class EncodeRep r where - encodeRep :: r -> Json - -instance encodeRepNoConstructors :: EncodeRep Rep.NoConstructors where - encodeRep r = encodeRep r - -instance encodeRepSum :: (EncodeRep a, EncodeRep b) => EncodeRep (Rep.Sum a b) where - encodeRep (Rep.Inl a) = encodeRep a - encodeRep (Rep.Inr b) = encodeRep b - -instance encodeRepConstructor :: (IsSymbol name, EncodeRepArgs a) => EncodeRep (Rep.Constructor name a) where - encodeRep (Rep.Constructor a) = - fromObject - $ SM.insert "tag" (fromString (reflectSymbol (SProxy :: SProxy name))) - $ SM.insert "values" (fromArray (encodeRepArgs a)) - $ SM.empty - -class EncodeRepArgs r where - encodeRepArgs :: r -> Array Json - -instance encodeRepArgsNoArguments :: EncodeRepArgs Rep.NoArguments where - encodeRepArgs Rep.NoArguments = [] - -instance encodeRepArgsProduct :: (EncodeRepArgs a, EncodeRepArgs b) => EncodeRepArgs (Rep.Product a b) where - encodeRepArgs (Rep.Product a b) = encodeRepArgs a <> encodeRepArgs b - -instance encodeRepArgsArgument :: (EncodeJson a) => EncodeRepArgs (Rep.Argument a) where - encodeRepArgs (Rep.Argument a) = [encodeJson a] - -instance encodeRepArgsRec :: (EncodeRepFields fields) => EncodeRepArgs (Rep.Rec fields) where - encodeRepArgs (Rep.Rec fields) = [fromObject $ encodeRepFields fields] - -class EncodeRepFields r where - encodeRepFields :: r -> SM.StrMap Json - -instance encodeRepFieldsProduct :: (EncodeRepFields a, EncodeRepFields b) => EncodeRepFields (Rep.Product a b) where - encodeRepFields (Rep.Product a b) = - SM.union (encodeRepFields a) (encodeRepFields b) - -instance encodeRepFieldsField :: (IsSymbol field, EncodeJson a) => EncodeRepFields (Rep.Field field a) where - encodeRepFields (Rep.Field a) = - SM.singleton (reflectSymbol (SProxy :: SProxy field)) - (encodeJson a) - -genericEncodeJson :: forall a r. Rep.Generic a r => EncodeRep r => a -> Json -genericEncodeJson = encodeRep <<< Rep.from diff --git a/src/Data/Argonaut/Encode/Generic/Rep.purs b/src/Data/Argonaut/Encode/Generic/Rep.purs new file mode 100644 index 0000000..1995483 --- /dev/null +++ b/src/Data/Argonaut/Encode/Generic/Rep.purs @@ -0,0 +1,64 @@ +module Data.Argonaut.Encode.Generic.Rep ( + class EncodeRep, + class EncodeRepArgs, + class EncodeRepFields, + encodeRep, + encodeRepArgs, + encodeRepFields, + genericEncodeJson +) where + +import Prelude + +import Data.Argonaut.Encode.Class (class EncodeJson, encodeJson) +import Data.Argonaut.Core (Json, fromArray, fromObject, fromString) +import Data.Generic.Rep as Rep +import Data.StrMap as SM +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) + +class EncodeRep r where + encodeRep :: r -> Json + +instance encodeRepNoConstructors :: EncodeRep Rep.NoConstructors where + encodeRep r = encodeRep r + +instance encodeRepSum :: (EncodeRep a, EncodeRep b) => EncodeRep (Rep.Sum a b) where + encodeRep (Rep.Inl a) = encodeRep a + encodeRep (Rep.Inr b) = encodeRep b + +instance encodeRepConstructor :: (IsSymbol name, EncodeRepArgs a) => EncodeRep (Rep.Constructor name a) where + encodeRep (Rep.Constructor a) = + fromObject + $ SM.insert "tag" (fromString (reflectSymbol (SProxy :: SProxy name))) + $ SM.insert "values" (fromArray (encodeRepArgs a)) + $ SM.empty + +class EncodeRepArgs r where + encodeRepArgs :: r -> Array Json + +instance encodeRepArgsNoArguments :: EncodeRepArgs Rep.NoArguments where + encodeRepArgs Rep.NoArguments = [] + +instance encodeRepArgsProduct :: (EncodeRepArgs a, EncodeRepArgs b) => EncodeRepArgs (Rep.Product a b) where + encodeRepArgs (Rep.Product a b) = encodeRepArgs a <> encodeRepArgs b + +instance encodeRepArgsArgument :: (EncodeJson a) => EncodeRepArgs (Rep.Argument a) where + encodeRepArgs (Rep.Argument a) = [encodeJson a] + +instance encodeRepArgsRec :: (EncodeRepFields fields) => EncodeRepArgs (Rep.Rec fields) where + encodeRepArgs (Rep.Rec fields) = [fromObject $ encodeRepFields fields] + +class EncodeRepFields r where + encodeRepFields :: r -> SM.StrMap Json + +instance encodeRepFieldsProduct :: (EncodeRepFields a, EncodeRepFields b) => EncodeRepFields (Rep.Product a b) where + encodeRepFields (Rep.Product a b) = + SM.union (encodeRepFields a) (encodeRepFields b) + +instance encodeRepFieldsField :: (IsSymbol field, EncodeJson a) => EncodeRepFields (Rep.Field field a) where + encodeRepFields (Rep.Field a) = + SM.singleton (reflectSymbol (SProxy :: SProxy field)) + (encodeJson a) + +genericEncodeJson :: forall a r. Rep.Generic a r => EncodeRep r => a -> Json +genericEncodeJson = encodeRep <<< Rep.from diff --git a/test/Main.purs b/test/Main.purs index 45884b7..a1cb85a 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -8,9 +8,9 @@ import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, log) import Data.Argonaut.Core (stringify) import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson) -import Data.Argonaut.Decode.Generic (genericDecodeJson) +import Data.Argonaut.Decode.Generic.Rep (genericDecodeJson) import Data.Argonaut.Encode.Class (class EncodeJson, encodeJson) -import Data.Argonaut.Encode.Generic (genericEncodeJson) +import Data.Argonaut.Encode.Generic.Rep (genericEncodeJson) import Data.Either (Either(..)) import Data.Generic.Rep (class Generic) import Data.Generic.Rep.Show (genericShow) From 49ce140020455d4c3d94a5f09612ca820dee38f5 Mon Sep 17 00:00:00 2001 From: rightfold Date: Mon, 5 Jun 2017 14:10:21 +0200 Subject: [PATCH 3/3] Document genericEncodeJson and genericDecodeJson --- src/Data/Argonaut/Decode/Generic/Rep.purs | 1 + src/Data/Argonaut/Encode/Generic/Rep.purs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Data/Argonaut/Decode/Generic/Rep.purs b/src/Data/Argonaut/Decode/Generic/Rep.purs index a713b7f..1263916 100644 --- a/src/Data/Argonaut/Decode/Generic/Rep.purs +++ b/src/Data/Argonaut/Decode/Generic/Rep.purs @@ -81,6 +81,7 @@ instance decodeRepFieldsField :: (IsSymbol field, DecodeJson a) => DecodeRepFiel value <- mFail ("the field '" <> name <> "' is not present") (SM.lookup name js) Rep.Field <$> decodeJson value +-- | Decode `Json` representation of a value which has a `Generic` type. genericDecodeJson :: forall a r. Rep.Generic a r => DecodeRep r => Json -> Either String a genericDecodeJson = map Rep.to <<< decodeRep diff --git a/src/Data/Argonaut/Encode/Generic/Rep.purs b/src/Data/Argonaut/Encode/Generic/Rep.purs index 1995483..23cb0aa 100644 --- a/src/Data/Argonaut/Encode/Generic/Rep.purs +++ b/src/Data/Argonaut/Encode/Generic/Rep.purs @@ -60,5 +60,6 @@ instance encodeRepFieldsField :: (IsSymbol field, EncodeJson a) => EncodeRepFiel SM.singleton (reflectSymbol (SProxy :: SProxy field)) (encodeJson a) +-- | Encode any `Generic` data structure into `Json`. genericEncodeJson :: forall a r. Rep.Generic a r => EncodeRep r => a -> Json genericEncodeJson = encodeRep <<< Rep.from