on master help sign in
Merge pull request #443 from phadej/untagged-value

Add UntaggedValue to SumEncoding

commit490420559ceae082193ec570dedcde5e46d4b739 authorAdam Bergmark <adam@bergmark.nl> dateFri 8 Jul 2016, 14:16 (10 years ago) parent2d2b699064db05
11 files changed+244−15
Data/Aeson/TH.hs+33−4 Data/Aeson/Types/FromJSON.hs+48−9 Data/Aeson/Types/Internal.hs+14 Data/Aeson/Types/ToJSON.hs+62 changelog.md+2 tests/Encoders.hs+22 tests/Instances.hs+8 tests/Options.hs+5 tests/Properties.hs+24 tests/Types.hs+9 tests/UnitTests.hs+17−2
Data/Aeson/TH.hs+33−4
@@ -112,7 +112,7 @@ module Data.Aeson.TH
112112 , mkLiftParseJSON2
113113 ) where
114114
115−import Control.Applicative ( pure, (<$>), (<*>) )
115+import Control.Applicative ( pure, (<$>), (<*>), (<|>) )
116116import Data.Aeson ( Object, (.=), (.:)
117117 , ToJSON(..), FromJSON(..)
118118 , ToJSON1(..), FromJSON1(..)
@@ -437,7 +437,7 @@ sumToValue opts multiCons conName exp
437437 [|A.object|] `appE` listE
438438 [ infixApp (conTxt opts conName) [|(.=)|] exp
439439 ]
440−
440+ UntaggedValue -> exp
441441 | otherwise = exp
442442
443443nullarySumToValue :: Options -> Bool -> Name -> Q Exp
@@ -447,6 +447,7 @@ nullarySumToValue opts multiCons conName =
447447 [|A.object|] `appE` listE
448448 [ infixApp [|T.pack tagFieldName|] [|(.=)|] (conStr opts conName)
449449 ]
450+ UntaggedValue -> conStr opts conName
450451 _ -> sumToValue opts multiCons conName [e|toJSON ([] :: [()])|]
451452
452453-- | Generates code to generate the JSON encoding of a single constructor.
@@ -545,6 +546,7 @@ argsToValue jc tjs opts multiCons (RecC conName ts) = case (unwrapUnaryRecords o
545546 ObjectWithSingleField ->
546547 [|A.object|] `appE` listE
547548 [ infixApp (conTxt opts conName) [|(.=)|] exp ]
549+ UntaggedValue -> exp
548550 else exp
549551 ) []
550552
@@ -610,7 +612,7 @@ sumToEncoding opts multiCons conName exp
610612 ([|E.text (T.pack contentsFieldName)|] <:> fexp)
611613 ObjectWithSingleField ->
612614 object (encStr opts conName <:> fexp)
613−
615+ UntaggedValue -> exp
614616 | otherwise = exp
615617
616618nullarySumToEncoding :: Options -> Bool -> Name -> Q Exp
@@ -619,6 +621,7 @@ nullarySumToEncoding opts multiCons conName =
619621 TaggedObject{tagFieldName} ->
620622 object $
621623 [|E.text (T.pack tagFieldName)|] <:> encStr opts conName
624+ UntaggedValue -> encStr opts conName
622625 _ -> sumToEncoding opts multiCons conName [e|toEncoding ([] :: [()])|]
623626
624627-- | Generates code to generate the JSON encoding of a single constructor.
@@ -706,6 +709,7 @@ argsToEncoding jc tes opts multiCons (RecC conName ts) = case (unwrapUnaryRecord
706709 objBody
707710 ObjectWithSingleField -> object $
708711 encStr opts conName <:> exp
712+ UntaggedValue -> exp
709713 else exp
710714 ) []
711715
@@ -836,7 +840,10 @@ consFromJSON jc tName opts cons = do
836840 where
837841 lamExpr value pjs = case cons of
838842 [con] -> parseArgs jc pjs tName opts con (Right value)
839− _ -> caseE (varE value) $
843+ _ | sumEncoding opts == UntaggedValue
844+ -> parseUntaggedValue pjs cons value
845+ | otherwise
846+ -> caseE (varE value) $
840847 if allNullaryToStringTag opts && all isNullary cons
841848 then allNullaryMatches
842849 else mixedMatches pjs
@@ -878,6 +885,7 @@ consFromJSON jc tName opts cons = do
878885 case sumEncoding opts of
879886 TaggedObject {tagFieldName, contentsFieldName} ->
880887 parseObject $ parseTaggedObject pjs tagFieldName contentsFieldName
888+ UntaggedValue -> error "UntaggedValue: Should be handled already"
881889 ObjectWithSingleField ->
882890 parseObject $ parseObjectWithSingleField pjs
883891 TwoElemArray ->
@@ -927,6 +935,27 @@ consFromJSON jc tName opts cons = do
927935 , noBindS $ parseContents pjs conKey (Left (valFieldName, obj)) 'conNotFoundFailTaggedObject
928936 ]
929937
938+ parseUntaggedValue pjs cons' conVal =
939+ foldr1 (\e e' -> infixApp e [|(<|>)|] e')
940+ (map (\x -> parseValue pjs x conVal) cons')
941+
942+ parseValue _pjs (NormalC conName []) conVal = do
943+ str <- newName "str"
944+ caseE (varE conVal)
945+ [ match (conP 'String [varP str])
946+ (guardedB
947+ [ liftM2 (,) (normalG $ infixApp (varE str) [|(==)|] ([|T.pack|] `appE` conStringE opts conName)
948+ )
949+ ([|pure|] `appE` conE conName)
950+ ]
951+ )
952+ []
953+ , matchFailed tName conName "String"
954+ ]
955+ parseValue pjs con conVal =
956+ parseArgs jc pjs tName opts con (Right conVal)
957+
958+
930959 parse2ElemArray pjs arr = do
931960 conKey <- newName "conKey"
932961 conVal <- newName "conVal"
Data/Aeson/Types/FromJSON.hs+48−9
@@ -774,17 +774,19 @@ class ParseSum arity f allNullary where
774774 parseSum :: Options -> FromArgs arity a
775775 -> Value -> Tagged allNullary (Parser (f a))
776776
777−instance ( SumFromString (a :+: b)
778− , FromPair arity (a :+: b)
779− , FromTaggedObject arity (a :+: b)
780− ) => ParseSum arity (a :+: b) True where
777+instance ( SumFromString (a :+: b)
778+ , FromPair arity (a :+: b)
779+ , FromTaggedObject arity (a :+: b)
780+ , FromUntaggedValue arity (a :+: b)
781+ ) => ParseSum arity (a :+: b) True where
781782 parseSum opts fargs
782783 | allNullaryToStringTag opts = Tagged . parseAllNullarySum opts
783784 | otherwise = Tagged . parseNonAllNullarySum opts fargs
784785
785−instance ( FromPair arity (a :+: b)
786− , FromTaggedObject arity (a :+: b)
787− ) => ParseSum arity (a :+: b) False where
786+instance ( FromPair arity (a :+: b)
787+ , FromTaggedObject arity (a :+: b)
788+ , FromUntaggedValue arity (a :+: b)
789+ ) => ParseSum arity (a :+: b) False where
788790 parseSum opts fargs = Tagged . parseNonAllNullarySum opts fargs
789791
790792--------------------------------------------------------------------------------
@@ -810,8 +812,9 @@ instance (Constructor c) => SumFromString (C1 c U1) where
810812
811813--------------------------------------------------------------------------------
812814
813−parseNonAllNullarySum :: ( FromPair arity (a :+: b)
814− , FromTaggedObject arity (a :+: b)
815+parseNonAllNullarySum :: ( FromPair arity (a :+: b)
816+ , FromTaggedObject arity (a :+: b)
817+ , FromUntaggedValue arity (a :+: b)
815818 ) => Options -> FromArgs arity c
816819 -> Value -> Parser ((a :+: b) c)
817820parseNonAllNullarySum opts fargs =
@@ -838,6 +841,8 @@ parseNonAllNullarySum opts fargs =
838841 _ -> fail "First element is not a String"
839842 else fail "Array doesn't have 2 elements"
840843
844+ UntaggedValue -> parseUntaggedValue opts fargs
845+
841846--------------------------------------------------------------------------------
842847
843848class FromTaggedObject arity f where
@@ -1001,6 +1006,40 @@ instance ( Constructor c
10011006
10021007--------------------------------------------------------------------------------
10031008
1009+class FromUntaggedValue arity f where
1010+ parseUntaggedValue :: Options -> FromArgs arity a
1011+ -> Value -> Parser (f a)
1012+
1013+instance
1014+ ( FromUntaggedValue arity a
1015+ , FromUntaggedValue arity b
1016+ ) => FromUntaggedValue arity (a :+: b)
1017+ where
1018+ parseUntaggedValue opts fargs value =
1019+ L1 <$> parseUntaggedValue opts fargs value <|>
1020+ R1 <$> parseUntaggedValue opts fargs value
1021+
1022+instance OVERLAPPABLE_
1023+ ( GFromJSON arity a
1024+ , ConsFromJSON arity a
1025+ ) => FromUntaggedValue arity (C1 c a)
1026+ where
1027+ parseUntaggedValue = gParseJSON
1028+
1029+instance OVERLAPPING_
1030+ ( Constructor c )
1031+ => FromUntaggedValue arity (C1 c U1)
1032+ where
1033+ parseUntaggedValue opts _ (String s)
1034+ | s == pack (constructorTagModifier opts (conName (undefined :: t c U1 p))) =
1035+ pure $ M1 U1
1036+ | otherwise =
1037+ fail $ "Invalid tag: " ++ unpack s
1038+ parseUntaggedValue _ _ v = typeMismatch (conName (undefined :: t c U1 p)) v
1039+
1040+
1041+--------------------------------------------------------------------------------
1042+
10041043notFound :: Text -> Parser a
10051044notFound key = fail $ "The key \"" ++ unpack key ++ "\" was not found"
10061045{-# INLINE notFound #-}
Data/Aeson/Types/Internal.hs+14
@@ -549,6 +549,20 @@ data SumEncoding =
549549 -- by the encoded value of that field! If the constructor is not a
550550 -- record the encoded constructor contents will be stored under
551551 -- the 'contentsFieldName' field.
552+ | UntaggedValue
553+ -- ^ Constructor names won't be encoded. Instead only the contents of the
554+ -- constructor will be encoded as if the type had single constructor. JSON
555+ -- encodings have to be disjoint for decoding to work properly.
556+ --
557+ -- When decoding, constructors are tried in the order of definition. If some
558+ -- encodings overlap, the first one defined will succeed.
559+ --
560+ -- /Note:/ Nullary constructors are encoded as the string (using
561+ -- 'constructorTagModifier'). Having a nullary constructor alongside a
562+ -- single field constructor that encodes to a string leads to ambiguity.
563+ --
564+ -- /Note:/ Only the last error is kept when decoding, so in the case of
565+ -- mailformed JSON, only an error for the last constructor will be reported.
552566 | ObjectWithSingleField
553567 -- ^ A constructor will be encoded to an object with a single
554568 -- field named after the constructor tag (modified by the
Data/Aeson/Types/ToJSON.hs+62
@@ -755,6 +755,7 @@ instance ( GetConName f
755755 , TaggedObjectPairs arity f
756756 , ObjectWithSingleFieldObj arity f
757757 , TwoElemArrayObj arity f
758+ , UntaggedValueObj arity f
758759 ) => SumToJSON arity f True where
759760 sumToJSON opts targs
760761 | allNullaryToStringTag opts = Tagged . String . pack
@@ -764,12 +765,14 @@ instance ( GetConName f
764765instance ( TwoElemArrayObj arity f
765766 , TaggedObjectPairs arity f
766767 , ObjectWithSingleFieldObj arity f
768+ , UntaggedValueObj arity f
767769 ) => SumToJSON arity f False where
768770 sumToJSON opts targs = Tagged . nonAllNullarySumToJSON opts targs
769771
770772nonAllNullarySumToJSON :: ( TwoElemArrayObj arity f
771773 , TaggedObjectPairs arity f
772774 , ObjectWithSingleFieldObj arity f
775+ , UntaggedValueObj arity f
773776 ) => Options -> ToArgs Value arity a
774777 -> f a -> Value
775778nonAllNullarySumToJSON opts targs =
@@ -778,6 +781,7 @@ nonAllNullarySumToJSON opts targs =
778781 object . taggedObjectPairs opts targs tagFieldName contentsFieldName
779782 ObjectWithSingleField -> Object . objectWithSingleFieldObj opts targs
780783 TwoElemArray -> Array . twoElemArrayObj opts targs
784+ UntaggedValue -> untaggedValueObj opts targs
781785
782786--------------------------------------------------------------------------------
783787
@@ -789,6 +793,7 @@ instance ( GetConName f
789793 , TaggedObjectEnc arity f
790794 , ObjectWithSingleFieldEnc arity f
791795 , TwoElemArrayEnc arity f
796+ , UntaggedValueEnc arity f
792797 ) => SumToEncoding arity f True where
793798 sumToEncoding opts targs
794799 | allNullaryToStringTag opts = Tagged . toEncoding .
@@ -798,12 +803,14 @@ instance ( GetConName f
798803instance ( TwoElemArrayEnc arity f
799804 , TaggedObjectEnc arity f
800805 , ObjectWithSingleFieldEnc arity f
806+ , UntaggedValueEnc arity f
801807 ) => SumToEncoding arity f False where
802808 sumToEncoding opts targs = Tagged . nonAllNullarySumToEncoding opts targs
803809
804810nonAllNullarySumToEncoding :: ( TwoElemArrayEnc arity f
805811 , TaggedObjectEnc arity f
806812 , ObjectWithSingleFieldEnc arity f
813+ , UntaggedValueEnc arity f
807814 ) => Options -> ToArgs Encoding arity a
808815 -> f a -> Encoding
809816nonAllNullarySumToEncoding opts targs =
@@ -812,6 +819,7 @@ nonAllNullarySumToEncoding opts targs =
812819 taggedObjectEnc opts targs tagFieldName contentsFieldName
813820 ObjectWithSingleField -> objectWithSingleFieldEnc opts targs
814821 TwoElemArray -> twoElemArrayEnc opts targs
822+ UntaggedValue -> untaggedValueEnc opts targs
815823
816824--------------------------------------------------------------------------------
817825
@@ -1172,6 +1180,60 @@ instance ( GToEncoding arity a
11721180 gToEncoding opts targs v ><
11731181 E.closeCurly
11741182
1183+--------------------------------------------------------------------------------
1184+
1185+class UntaggedValueObj arity f where
1186+ untaggedValueObj :: Options -> ToArgs Value arity a
1187+ -> f a -> Value
1188+
1189+instance
1190+ ( UntaggedValueObj arity a
1191+ , UntaggedValueObj arity b
1192+ ) => UntaggedValueObj arity (a :+: b)
1193+ where
1194+ untaggedValueObj opts targs (L1 x) = untaggedValueObj opts targs x
1195+ untaggedValueObj opts targs (R1 x) = untaggedValueObj opts targs x
1196+
1197+instance OVERLAPPABLE_
1198+ ( GToJSON arity a
1199+ , ConsToJSON arity a
1200+ ) => UntaggedValueObj arity (C1 c a) where
1201+ untaggedValueObj = gToJSON
1202+
1203+instance OVERLAPPING_
1204+ ( Constructor c )
1205+ => UntaggedValueObj arity (C1 c U1)
1206+ where
1207+ untaggedValueObj opts _ _ = toJSON $
1208+ constructorTagModifier opts $ conName (undefined :: t c U1 p)
1209+
1210+--------------------------------------------------------------------------------
1211+
1212+class UntaggedValueEnc arity f where
1213+ untaggedValueEnc :: Options -> ToArgs Encoding arity a
1214+ -> f a -> Encoding
1215+instance
1216+ ( UntaggedValueEnc arity a
1217+ , UntaggedValueEnc arity b
1218+ ) => UntaggedValueEnc arity (a :+: b)
1219+ where
1220+ untaggedValueEnc opts targs (L1 x) = untaggedValueEnc opts targs x
1221+ untaggedValueEnc opts targs (R1 x) = untaggedValueEnc opts targs x
1222+
1223+instance OVERLAPPABLE_
1224+ ( GToEncoding arity a
1225+ , ConsToEncoding arity a
1226+ ) => UntaggedValueEnc arity (C1 c a)
1227+ where
1228+ untaggedValueEnc = gToEncoding
1229+
1230+instance OVERLAPPING_
1231+ ( Constructor c )
1232+ => UntaggedValueEnc arity (C1 c U1)
1233+ where
1234+ untaggedValueEnc opts _ _ = toEncoding $
1235+ constructorTagModifier opts $ conName (undefined :: t c U1 p)
1236+
11751237-------------------------------------------------------------------------------
11761238-- Instances
11771239-------------------------------------------------------------------------------
changelog.md+2
@@ -16,6 +16,8 @@ For the latest version of this document, please see [https://github.com/bos/aeso
1616
1717* Added `Data.Aeson.Encoding` with functions to work with `Encoding` in an abstract way.
1818
19+* Added `UntaggedValue` to `SumEncoding`
20+
1921* Export `decodeWith`, `decodeStrictWith`, `eitherDecodeWith`, and
2022 `eitherDecodeStrictWith` from `Data.Aeson.Parser`. This allows
2123 decoding using explicit parsers instead of using `FromJSON`
tests/Encoders.hs+22
@@ -232,6 +232,28 @@ gSomeTypeToJSONOmitNothingFields = genericToJSON optsOmitNothingFields
232232gSomeTypeToEncodingOmitNothingFields :: SomeType Int -> Encoding
233233gSomeTypeToEncodingOmitNothingFields = genericToEncoding optsOmitNothingFields
234234
235+-------------------------------------------------------------------------------
236+-- EitherTextInt encoders/decodes
237+-------------------------------------------------------------------------------
238+
239+thEitherTextIntToJSONUntaggedValue :: EitherTextInt -> Value
240+thEitherTextIntToJSONUntaggedValue = $(mkToJSON optsUntaggedValue ''EitherTextInt)
241+
242+thEitherTextIntToEncodingUntaggedValue :: EitherTextInt -> Encoding
243+thEitherTextIntToEncodingUntaggedValue = $(mkToEncoding optsUntaggedValue ''EitherTextInt)
244+
245+thEitherTextIntParseJSONUntaggedValue :: Value -> Parser EitherTextInt
246+thEitherTextIntParseJSONUntaggedValue = $(mkParseJSON optsUntaggedValue ''EitherTextInt)
247+
248+
249+gEitherTextIntToJSONUntaggedValue :: EitherTextInt -> Value
250+gEitherTextIntToJSONUntaggedValue = genericToJSON optsUntaggedValue
251+
252+gEitherTextIntToEncodingUntaggedValue :: EitherTextInt -> Encoding
253+gEitherTextIntToEncodingUntaggedValue = genericToEncoding optsUntaggedValue
254+
255+gEitherTextIntParseJSONUntaggedValue :: Value -> Parser EitherTextInt
256+gEitherTextIntParseJSONUntaggedValue = genericParseJSON optsUntaggedValue
235257
236258--------------------------------------------------------------------------------
237259-- Approx encoders/decoders
tests/Instances.hs+8
@@ -155,6 +155,14 @@ instance Arbitrary a => Arbitrary (SomeType a) where
155155 , Record <$> arbitrary <*> arbitrary <*> arbitrary
156156 ]
157157
158+instance Arbitrary EitherTextInt where
159+ arbitrary = oneof
160+ [ LeftBool <$> arbitrary
161+ , RightInt <$> arbitrary
162+ , BothTextInt <$> arbitrary <*> arbitrary
163+ , pure NoneNullary
164+ ]
165+
158166instance Arbitrary (GADT String) where
159167 arbitrary = GADT <$> arbitrary
160168
tests/Options.hs+5
@@ -34,3 +34,8 @@ optsOmitNothingFields :: Options
3434optsOmitNothingFields = optsDefault
3535 { omitNothingFields = True
3636 }
37+
38+optsUntaggedValue :: Options
39+optsUntaggedValue = optsDefault
40+ { sumEncoding = UntaggedValue
41+ }
tests/Properties.hs+24
@@ -172,6 +172,16 @@ isObjectWithSingleField :: Value -> Bool
172172isObjectWithSingleField (Object obj) = H.size obj == 1
173173isObjectWithSingleField _ = False
174174
175+-- | is untaggedValue of EitherTextInt
176+isUntaggedValueETI :: Value -> Bool
177+isUntaggedValueETI (String s)
178+ | s == "nonenullary" = True
179+isUntaggedValueETI (Bool _) = True
180+isUntaggedValueETI (Number _) = True
181+isUntaggedValueETI (Array a) = length a == 2
182+isUntaggedValueETI _ = False
183+
184+
175185--------------------------------------------------------------------------------
176186
177187
@@ -293,6 +303,10 @@ tests = testGroup "properties" [
293303 , testProperty "ObjectWithSingleField" (toParseJSON gNullaryParseJSONObjectWithSingleField gNullaryToJSONObjectWithSingleField)
294304 ]
295305 ]
306+ , testGroup "EitherTextInt" [
307+ testProperty "UntaggedValue" (isUntaggedValueETI . gEitherTextIntToJSONUntaggedValue)
308+ , testProperty "roundtrip" (toParseJSON gEitherTextIntParseJSONUntaggedValue gEitherTextIntToJSONUntaggedValue)
309+ ]
296310 , testGroup "SomeType" [
297311 testProperty "2ElemArray" (is2ElemArray . gSomeTypeToJSON2ElemArray)
298312 , testProperty "TaggedObject" (isTaggedObject . gSomeTypeToJSONTaggedObject)
@@ -325,6 +339,9 @@ tests = testGroup "properties" [
325339 , testProperty "ApproxDefault" $
326340 gApproxToJSONDefault `sameAs` gApproxToEncodingDefault
327341
342+ , testProperty "EitherTextInt UntaggedValue" $
343+ gEitherTextIntToJSONUntaggedValue `sameAs` gEitherTextIntToEncodingUntaggedValue
344+
328345 , testProperty "SomeType2ElemArray" $
329346 gSomeTypeToJSON2ElemArray `sameAs` gSomeTypeToEncoding2ElemArray
330347#if __GLASGOW_HASKELL__ >= 706
@@ -371,6 +388,10 @@ tests = testGroup "properties" [
371388 , testProperty "ObjectWithSingleField" (toParseJSON thNullaryParseJSONObjectWithSingleField thNullaryToJSONObjectWithSingleField)
372389 ]
373390 ]
391+ , testGroup "EitherTextInt" [
392+ testProperty "UntaggedValue" (isUntaggedValueETI . thEitherTextIntToJSONUntaggedValue)
393+ , testProperty "roundtrip" (toParseJSON thEitherTextIntParseJSONUntaggedValue thEitherTextIntToJSONUntaggedValue)
394+ ]
374395 , testGroup "SomeType" [
375396 testProperty "2ElemArray" (is2ElemArray . thSomeTypeToJSON2ElemArray)
376397 , testProperty "TaggedObject" (isTaggedObject . thSomeTypeToJSONTaggedObject)
@@ -418,6 +439,9 @@ tests = testGroup "properties" [
418439 , testProperty "ApproxDefault" $
419440 thApproxToJSONDefault `sameAs` thApproxToEncodingDefault
420441
442+ , testProperty "EitherTextInt UntaggedValue" $
443+ thEitherTextIntToJSONUntaggedValue `sameAs` thEitherTextIntToEncodingUntaggedValue
444+
421445 , testProperty "SomeType2ElemArray" $
422446 thSomeTypeToJSON2ElemArray `sameAs` thSomeTypeToEncoding2ElemArray
423447 , testProperty "SomeType2ElemArray unary" $
tests/Types.hs+9
@@ -65,6 +65,14 @@ data SomeType a = Nullary
6565 , testThree :: Maybe a
6666 } deriving (Eq, Show)
6767
68+-- Used for testing UntaggedValue SumEncoding
69+data EitherTextInt
70+ = LeftBool Bool
71+ | RightInt Int
72+ | BothTextInt Text Int
73+ | NoneNullary
74+ deriving (Eq, Show)
75+
6876data GADT a where
6977 GADT :: { gadt :: String } -> GADT String
7078 deriving Typeable
@@ -85,6 +93,7 @@ deriving instance Generic (SomeType a)
8593#if __GLASGOW_HASKELL__ >= 706
8694deriving instance Generic1 SomeType
8795#endif
96+deriving instance Generic EitherTextInt
8897
8998failure :: Show a => String -> String -> a -> Property
9099failure func msg v = counterexample
tests/UnitTests.hs+17−2
@@ -15,11 +15,11 @@ import Instances ()
1515
1616import Control.Applicative (Const(..))
1717import Control.Monad (forM)
18−import Data.Aeson (FromJSONKeyFunction(..), FromJSONKey(..), decode, eitherDecode, encode, genericToJSON, genericToEncoding, object, FromJSON(..), withObject, (.=), (.:), (.:?), (.:!))
18+import Data.Aeson (FromJSONKeyFunction(..), FromJSONKey(..), decode, eitherDecode, encode, genericParseJSON, genericToJSON, genericToEncoding, object, FromJSON(..), withObject, (.=), (.:), (.:?), (.:!))
1919import Data.Aeson.Text (encodeToTextBuilder)
2020import Data.Aeson.Internal (JSONPathElement(..), formatError)
2121import Data.Aeson.TH (deriveJSON)
22−import Data.Aeson.Types (ToJSON(..), Value, camelTo, camelTo2, defaultOptions, omitNothingFields)
22+import Data.Aeson.Types (ToJSON(..), Value, camelTo, camelTo2, defaultOptions, omitNothingFields, Options(..), SumEncoding(..))
2323import Data.Char (toUpper)
2424import Data.Hashable (hash)
2525import Data.List.NonEmpty (NonEmpty(..))
@@ -108,6 +108,18 @@ camelFrom c s = let (p:ps) = split c s
108108 split c' s' = map L.unpack $ L.split c' $ L.pack s'
109109 capitalize t = toUpper (head t) : tail t
110110
111+
112+data MyEither a b = MyLeft a | MyRight b
113+ deriving (Generic, Show, Eq)
114+
115+instance (ToJSON a, ToJSON b) => ToJSON (MyEither a b) where
116+ toJSON = genericToJSON defaultOptions { sumEncoding = UntaggedValue }
117+ toEncoding = genericToEncoding defaultOptions { sumEncoding = UntaggedValue }
118+
119+instance (FromJSON a, FromJSON b) => FromJSON (MyEither a b) where
120+ parseJSON = genericParseJSON defaultOptions { sumEncoding = UntaggedValue }
121+
122+
111123data Wibble = Wibble {
112124 wibbleString :: String
113125 , wibbleInt :: Int
@@ -395,6 +407,9 @@ jsonExamples =
395407 , Example "Compose3' [] I [] Char" "[\"x\"]" (pure 'x' :: Compose3' [] I [] Char)
396408 , Example "Compose3' [] [] I Char" "[\"x\"]" (pure 'x' :: Compose3' [] [] I Char)
397409 , Example "Compose3' [] [] [] Char" "[[\"x\"]]" (pure 'x' :: Compose3' [] [] [] Char)
410+
411+ , Example "MyEither Int String: Left" "42" (MyLeft 42 :: MyEither Int String)
412+ , Example "MyEither Int String: Right" "\"foo\"" (MyRight "foo" :: MyEither Int String)
398413 ]
399414
400415