From 3438cbcad36a5ee2c2db2f8a4693ebc50f97eafd Mon Sep 17 00:00:00 2001 From: Poscat Date: Mon, 18 Jan 2021 00:03:32 +0800 Subject: [PATCH 01/15] Implement generic encoding and decoding for TaggingFlatObject --- src/Data/Aeson/Types/FromJSON.hs | 74 ++++++++++++++++++++++++++++++++ src/Data/Aeson/Types/Internal.hs | 2 + src/Data/Aeson/Types/ToJSON.hs | 65 ++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) diff --git a/src/Data/Aeson/Types/FromJSON.hs b/src/Data/Aeson/Types/FromJSON.hs index cd39ac3e9..b4c65fe78 100644 --- a/src/Data/Aeson/Types/FromJSON.hs +++ b/src/Data/Aeson/Types/FromJSON.hs @@ -10,6 +10,7 @@ {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE UndecidableInstances #-} @@ -158,6 +159,8 @@ import qualified Data.Primitive.Types as PM import qualified Data.Primitive.PrimArray as PM import Data.Coerce (Coercible, coerce) +import GHC.TypeNats +import Data.Kind (Type) parseIndexedJSON :: (Value -> Parser a) -> Int -> Value -> Parser a parseIndexedJSON p idx value = p value Index idx @@ -1010,6 +1013,7 @@ instance ( ConstructorNames f , FromPair arity f , FromTaggedObject arity f , FromUntaggedValue arity f + , FromTaggedFlatObject arity f ) => ParseSum arity f True where parseSum p@(tname :* opts :* _) | allNullaryToStringTag opts = Tagged . parseAllNullarySum tname opts @@ -1019,6 +1023,7 @@ instance ( ConstructorNames f , FromPair arity f , FromTaggedObject arity f , FromUntaggedValue arity f + , FromTaggedFlatObject arity f ) => ParseSum arity f False where parseSum p = Tagged . parseNonAllNullarySum p @@ -1101,6 +1106,7 @@ parseNonAllNullarySum :: forall f c arity. ( FromPair arity f , FromTaggedObject arity f , FromUntaggedValue arity f + , FromTaggedFlatObject arity f , ConstructorNames f ) => TypeName :* Options :* FromArgs arity c -> Value -> Parser (f c) @@ -1118,6 +1124,17 @@ parseNonAllNullarySum p@(tname :* opts :* _) = ", but found tag " ++ show tag cnames_ = unTagged2 (constructorTags (constructorTagModifier opts) :: Tagged2 f [String]) + TaggedFlatObject{..} -> + withObject tname $ \obj -> do + let tagKey = pack tagFieldName + badTag tag = failWith_ $ \cnames -> + "expected tag field to be one of " ++ show cnames ++ + ", but found tag " ++ show tag + cnames_ = unTagged2 (constructorTags (constructorTagModifier opts) :: Tagged2 f [String]) + tag <- contextType tname . contextTag tagKey cnames_ $ obj .: tagKey + fromMaybe (badTag tag Key tagKey) $ + parseTaggedFlatObject (tag :* p) obj + ObjectWithSingleField -> withObject tname $ \obj -> case H.toList obj of [(tag, v)] -> maybe (badTag tag) ( Key tag) $ @@ -1401,6 +1418,63 @@ instance ( Constructor c -------------------------------------------------------------------------------- +class FromTaggedFlatObject arity f where + parseTaggedFlatObject :: Text :* TypeName :* Options :* FromArgs arity a + -> Object + -> Maybe (Parser (f a)) + +instance ( FromTaggedFlatObject arity f + , FromTaggedFlatObject arity g + ) => FromTaggedFlatObject arity (f :+: g) where + parseTaggedFlatObject p obj = + (fmap L1 <$> parseTaggedFlatObject p obj) <|> + (fmap R1 <$> parseTaggedFlatObject p obj) + +instance ( IsRecord f isRecord + , FromTaggedFlatObject' arity f isRecord + , Constructor c + ) => FromTaggedFlatObject arity (C1 c f) where + parseTaggedFlatObject (tag :* p@(_ :* opts :* _)) obj + | tag == tag' = Just $ fmap M1 $ (unTagged @Type @isRecord) $ parseTaggedFlatObject' (cname :* p) obj + | otherwise = Nothing + where + tag' = pack $ constructorTagModifier opts cname + cname = conName (undefined :: M1 i c a p) + +class FromTaggedFlatObject' arity f isRecord where + parseTaggedFlatObject' :: ConName :* TypeName :* Options :* FromArgs arity a + -> Object + -> Tagged isRecord (Parser (f a)) + +instance (RecordFromJSON arity f, FieldNames f) => FromTaggedFlatObject' arity f True where + parseTaggedFlatObject' p = Tagged . recordParseJSON (True :* p) + +instance FromTaggedFlatObject' arity U1 False where + parseTaggedFlatObject' _ _ = Tagged (pure U1) + +instance OVERLAPPABLE_ (PositionFromObject 1 arity f) => FromTaggedFlatObject' arity f False where + parseTaggedFlatObject' (_ :* p) obj = Tagged (positionFromObject (Proxy @1) p obj) + +class KnownNat n => PositionFromObject n arity f where + positionFromObject :: Proxy n + -> TypeName :* Options :* FromArgs arity a + -> Object + -> Parser (f a) + +instance (KnownNat n, GFromJSON arity a) => PositionFromObject n arity (S1 m a) where + positionFromObject _ (_ :* opts :* fargs) obj = + explicitParseField (gParseJSON opts fargs) obj $ pack $ show $ natVal $ Proxy @n + +instance ( PositionFromObject n arity f + , PositionFromObject (n+1) arity g + ) => PositionFromObject n arity (f :*: g) where + positionFromObject _ p obj = + (:*:) + <$> positionFromObject (Proxy @n) p obj + <*> positionFromObject (Proxy @(n+1)) p obj + +-------------------------------------------------------------------------------- + class FromUntaggedValue arity f where parseUntaggedValue :: TypeName :* Options :* FromArgs arity a -> Value diff --git a/src/Data/Aeson/Types/Internal.hs b/src/Data/Aeson/Types/Internal.hs index ac92f987b..d2f15e45e 100644 --- a/src/Data/Aeson/Types/Internal.hs +++ b/src/Data/Aeson/Types/Internal.hs @@ -682,6 +682,8 @@ data SumEncoding = -- by the encoded value of that field! If the constructor is not a -- record the encoded constructor contents will be stored under -- the 'contentsFieldName' field. + | TaggedFlatObject { tagFieldName :: String } + -- ^ A constructor will be encoded to an object with a field | UntaggedValue -- ^ Constructor names won't be encoded. Instead only the contents of the -- constructor will be encoded as if the type had a single constructor. JSON diff --git a/src/Data/Aeson/Types/ToJSON.hs b/src/Data/Aeson/Types/ToJSON.hs index 5908e70ed..bf791e74e 100644 --- a/src/Data/Aeson/Types/ToJSON.hs +++ b/src/Data/Aeson/Types/ToJSON.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE EmptyDataDecls #-} @@ -141,6 +142,7 @@ import qualified Data.Primitive.Array as PM import qualified Data.Primitive.SmallArray as PM import qualified Data.Primitive.Types as PM import qualified Data.Primitive.PrimArray as PM +import GHC.TypeNats toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b) @@ -851,6 +853,7 @@ class SumToJSON enc arity f allNullary where instance ( GetConName f , FromString enc , TaggedObject enc arity f + , TaggedFlatObject enc arity f , SumToJSON' ObjectWithSingleField enc arity f , SumToJSON' TwoElemArray enc arity f , SumToJSON' UntaggedValue enc arity f @@ -862,6 +865,7 @@ instance ( GetConName f | otherwise = Tagged . nonAllNullarySumToJSON opts targs instance ( TaggedObject enc arity f + , TaggedFlatObject enc arity f , SumToJSON' ObjectWithSingleField enc arity f , SumToJSON' TwoElemArray enc arity f , SumToJSON' UntaggedValue enc arity f @@ -870,6 +874,7 @@ instance ( TaggedObject enc arity f sumToJSON opts targs = Tagged . nonAllNullarySumToJSON opts targs nonAllNullarySumToJSON :: ( TaggedObject enc arity f + , TaggedFlatObject enc arity f , SumToJSON' ObjectWithSingleField enc arity f , SumToJSON' TwoElemArray enc arity f , SumToJSON' UntaggedValue enc arity f @@ -881,6 +886,9 @@ nonAllNullarySumToJSON opts targs = TaggedObject{..} -> taggedObject opts targs tagFieldName contentsFieldName + TaggedFlatObject{..} -> + taggedFlatObject opts targs tagFieldName + ObjectWithSingleField -> (unTagged :: Tagged ObjectWithSingleField enc -> enc) . sumToJSON' opts targs @@ -906,6 +914,63 @@ instance FromString Value where -------------------------------------------------------------------------------- +class TaggedFlatObject enc arity f where + taggedFlatObject :: Options -> ToArgs enc arity a + -> String -> f a -> enc + +instance (TaggedFlatObject enc arity a + , TaggedFlatObject enc arity b + ) => TaggedFlatObject enc arity (a :+: b) + where + taggedFlatObject opts targs tagFieldName (L1 x) = + taggedFlatObject opts targs tagFieldName x + taggedFlatObject opts targs tagFieldName (R1 x) = + taggedFlatObject opts targs tagFieldName x + +instance ( IsRecord a isRecord + , TaggedFlatObject' enc pairs arity a isRecord + , KeyValuePair enc pairs + , FromString enc + , Constructor c + , FromPairs enc pairs + ) => TaggedFlatObject enc arity (C1 c a) where + taggedFlatObject opts targs tagFieldName (M1 a) = + fromPairs (tag `mappend` contents) + where + tag :: pairs + tag = tagFieldName `pair` + (fromString (constructorTagModifier opts (conName (undefined :: t c a p))) + :: enc) + contents :: pairs + contents = (unTagged :: Tagged isRecord pairs -> pairs) $ taggedFlatObject' opts targs a + +class TaggedFlatObject' enc pairs arity f isRecord where + taggedFlatObject' :: Options -> ToArgs enc arity a + -> f a -> Tagged isRecord pairs + +instance RecordToPairs pairs enc arity f => TaggedFlatObject' pairs enc arity f True where + taggedFlatObject' opts targs = Tagged . recordToPairs opts targs + +instance Monoid pairs => TaggedFlatObject' enc pairs arity U1 False where + taggedFlatObject' _ _ _ = Tagged mempty + +instance OVERLAPPABLE_ (PositionToPairs 1 pairs enc arity f) => TaggedFlatObject' enc pairs arity f False where + taggedFlatObject' opts targs a = Tagged $ positionToPairs (Proxy :: Proxy 1) opts targs a + +class KnownNat n => PositionToPairs n pairs enc arity f where + positionToPairs :: Proxy n -> Options -> ToArgs enc arity a -> f a -> pairs + +instance (KeyValuePair enc pairs, GToJSON' enc arity a, KnownNat n) => PositionToPairs n pairs enc arity (S1 m a) where + positionToPairs p opts targs (M1 a) = + show (natVal p) `pair` gToJSON opts targs a + +instance (Semigroup pairs, PositionToPairs n pairs enc arity f, PositionToPairs (n+1) pairs enc arity g) => PositionToPairs n pairs enc arity (f :*: g) where + positionToPairs _ opts targs (f :*: g) = + positionToPairs (Proxy :: Proxy n) opts targs f + <> positionToPairs (Proxy :: Proxy (n+1)) opts targs g + +-------------------------------------------------------------------------------- + class TaggedObject enc arity f where taggedObject :: Options -> ToArgs enc arity a -> String -> String From f6c0807fcb309e767f0819a65c9fa8f0ac4cf1e9 Mon Sep 17 00:00:00 2001 From: Poscat Date: Mon, 18 Jan 2021 11:59:41 +0800 Subject: [PATCH 02/15] Implement deriveToJSON for TaggedFlatObject --- src/Data/Aeson/TH.hs | 48 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/Data/Aeson/TH.hs b/src/Data/Aeson/TH.hs index 3dd63521b..e62366519 100644 --- a/src/Data/Aeson/TH.hs +++ b/src/Data/Aeson/TH.hs @@ -421,6 +421,7 @@ sumToValue target opts multiCons nullary conName value pairs content = pairs contentsFieldName in fromPairsE $ if nullary then tag else infixApp tag [|(Monoid.<>)|] content + TaggedFlatObject {} -> error "impossible" ObjectWithSingleField -> objectE [(conString opts conName, value)] UntaggedValue | nullary -> conStr target opts conName @@ -434,7 +435,21 @@ argsToValue :: ToJSONFun -> JSONClass -> TyVarMap -> Options -> Bool -> Construc argsToValue target jc tvMap opts multiCons ConstructorInfo { constructorName = conName , constructorVariant = NormalConstructor - , constructorFields = argTys } = do + , constructorFields = argTys } + | TaggedFlatObject{tagFieldName} <- sumEncoding opts + , multiCons = do + let tag = (tagFieldName, conStr target opts conName) + argTys' <- mapM resolveTypeSynonyms argTys + let len = length argTys' + args <- newNameList "arg" len + let os = zipWith (\arg argTy -> dispatchToJSON target jc conName tvMap argTy `appE` varE arg) args argTys' + pairs = zip (fmap (show :: Int -> String) [1..]) os + obj = objectE (tag : pairs) + match (conP conName $ map varP args) + (normalB obj) + [] + | otherwise = + do argTys' <- mapM resolveTypeSynonyms argTys let len = length argTys' args <- newNameList "arg" len @@ -491,14 +506,33 @@ argsToValue target jc tvMap opts multiCons else e arg match (conP conName $ map varP args) - (normalB $ recordSumToValue target opts multiCons (null argTys) conName pairs) + (normalB $ case () of + () + | TaggedFlatObject {tagFieldName} <- sumEncoding opts -> do + let tag = pairE tagFieldName (conStr target opts conName) + fromPairsE $ infixApp tag [|(<>)|] pairs + | otherwise -> recordSumToValue target opts multiCons (null argTys) conName pairs) [] -- Infix constructors. argsToValue target jc tvMap opts multiCons ConstructorInfo { constructorName = conName , constructorVariant = InfixConstructor - , constructorFields = argTys } = do + , constructorFields = argTys } + | TaggedFlatObject {tagFieldName} <- sumEncoding opts + , multiCons = do + [alTy, arTy] <- mapM resolveTypeSynonyms argTys + al <- newName "argL" + ar <- newName "argR" + let tag = (tagFieldName, conStr target opts conName) + os = zipWith (\arg argTy -> dispatchToJSON target jc conName tvMap argTy `appE` varE arg) [al, ar] [alTy, arTy] + pairs = zip (fmap (show :: Int -> String) [1..]) os + obj = objectE (tag : pairs) + match (infixP (varP al) conName (varP ar)) + (normalB obj) + [] + | otherwise = + do [alTy, arTy] <- mapM resolveTypeSynonyms argTys al <- newName "argL" ar <- newName "argR" @@ -729,6 +763,7 @@ consFromJSON jc tName opts instTys cons = do case sumEncoding opts of TaggedObject {tagFieldName, contentsFieldName} -> parseObject $ parseTaggedObject tvMap tagFieldName contentsFieldName + TaggedFlatObject {tagFieldName} -> error "unsupported" UntaggedValue -> error "UntaggedValue: Should be handled already" ObjectWithSingleField -> parseObject $ parseObjectWithSingleField tvMap @@ -779,6 +814,13 @@ consFromJSON jc tName opts instTys cons = do , noBindS $ parseContents tvMap conKey (Left (valFieldName, obj)) 'conNotFoundFailTaggedObject ] + parseTaggedFlatObject tvMap typFieldName obj = do + conKey <- newName "conKey" + doE [ bindS (varP conKey) + (infixApp (varE obj) [|(.:)|] ([|T.pack|] `appE` stringE typFieldName)) + , noBindS $ parseContents tvMap conKey (Right obj) 'conNotFoundFailTaggedObject + ] + parseUntaggedValue tvMap cons' conVal = foldr1 (\e e' -> infixApp e [|(<|>)|] e') (map (\x -> parseValue tvMap x conVal) cons') From 203a1b4d368ee7a474f9cf86ee316fe9ce048026 Mon Sep 17 00:00:00 2001 From: Poscat Date: Mon, 18 Jan 2021 18:24:21 +0800 Subject: [PATCH 03/15] Add generic test cases for TaggedFlatObject --- tests/Encoders.hs | 29 +++++++++++++++++++++++++++++ tests/Options.hs | 3 +++ tests/PropertyGeneric.hs | 14 ++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/tests/Encoders.hs b/tests/Encoders.hs index 8bcab3cfb..8584da18d 100644 --- a/tests/Encoders.hs +++ b/tests/Encoders.hs @@ -88,6 +88,16 @@ gNullaryParseJSONTaggedObject :: Value -> Parser Nullary gNullaryParseJSONTaggedObject = genericParseJSON optsTaggedObject +gNullaryToJSONTaggedFlatObject :: Nullary -> Value +gNullaryToJSONTaggedFlatObject = genericToJSON optsTaggedFlatObject + +gNullaryToEncodingTaggedFlatObject :: Nullary -> Encoding +gNullaryToEncodingTaggedFlatObject = genericToEncoding optsTaggedFlatObject + +gNullaryParseJSONTaggedFlatObject :: Value -> Parser Nullary +gNullaryParseJSONTaggedFlatObject = genericParseJSON optsTaggedFlatObject + + gNullaryToJSONObjectWithSingleField :: Nullary -> Value gNullaryToJSONObjectWithSingleField = genericToJSON optsObjectWithSingleField @@ -217,6 +227,25 @@ gSomeTypeLiftParseJSONTaggedObject :: LiftParseJSON SomeType a gSomeTypeLiftParseJSONTaggedObject = genericLiftParseJSON optsTaggedObject +gSomeTypeToJSONTaggedFlatObject :: SomeType Int -> Value +gSomeTypeToJSONTaggedFlatObject = genericToJSON optsTaggedFlatObject + +gSomeTypeToEncodingTaggedFlatObject :: SomeType Int -> Encoding +gSomeTypeToEncodingTaggedFlatObject = genericToEncoding optsTaggedFlatObject + +gSomeTypeParseJSONTaggedFlatObject :: Value -> Parser (SomeType Int) +gSomeTypeParseJSONTaggedFlatObject = genericParseJSON optsTaggedFlatObject + +gSomeTypeLiftToEncodingTaggedFlatObject :: LiftToEncoding SomeType a +gSomeTypeLiftToEncodingTaggedFlatObject = genericLiftToEncoding optsTaggedFlatObject + +gSomeTypeLiftToJSONTaggedFlatObject :: LiftToJSON SomeType a +gSomeTypeLiftToJSONTaggedFlatObject = genericLiftToJSON optsTaggedFlatObject + +gSomeTypeLiftParseJSONTaggedFlatObject :: LiftParseJSON SomeType a +gSomeTypeLiftParseJSONTaggedFlatObject = genericLiftParseJSON optsTaggedFlatObject + + gSomeTypeToJSONObjectWithSingleField :: SomeType Int -> Value gSomeTypeToJSONObjectWithSingleField = genericToJSON optsObjectWithSingleField diff --git a/tests/Options.hs b/tests/Options.hs index 8618750e7..ba26fe8c7 100644 --- a/tests/Options.hs +++ b/tests/Options.hs @@ -39,6 +39,9 @@ optsOmitNothingFields = optsDefault { omitNothingFields = True } +optsTaggedFlatObject :: Options +optsTaggedFlatObject = optsDefault { allNullaryToStringTag = False, sumEncoding = TaggedFlatObject "tag"} + optsUntaggedValue :: Options optsUntaggedValue = optsDefault { sumEncoding = UntaggedValue diff --git a/tests/PropertyGeneric.hs b/tests/PropertyGeneric.hs index 8f9ed8cff..1bf7a510e 100644 --- a/tests/PropertyGeneric.hs +++ b/tests/PropertyGeneric.hs @@ -23,11 +23,13 @@ genericTests = testProperty "string" (isString . gNullaryToJSONString) , testProperty "2ElemArray" (is2ElemArray . gNullaryToJSON2ElemArray) , testProperty "TaggedObject" (isNullaryTaggedObject . gNullaryToJSONTaggedObject) + , testProperty "TaggedFlatObject" (isNullaryTaggedObject . gNullaryToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (isObjectWithSingleField . gNullaryToJSONObjectWithSingleField) , testGroup "roundTrip" [ testProperty "string" (toParseJSON gNullaryParseJSONString gNullaryToJSONString) , testProperty "2ElemArray" (toParseJSON gNullaryParseJSON2ElemArray gNullaryToJSON2ElemArray) , testProperty "TaggedObject" (toParseJSON gNullaryParseJSONTaggedObject gNullaryToJSONTaggedObject) + , testProperty "TaggedFlatObject" (toParseJSON gNullaryParseJSONTaggedFlatObject gNullaryToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (toParseJSON gNullaryParseJSONObjectWithSingleField gNullaryToJSONObjectWithSingleField) ] ] @@ -38,14 +40,17 @@ genericTests = , testGroup "SomeType" [ testProperty "2ElemArray" (is2ElemArray . gSomeTypeToJSON2ElemArray) , testProperty "TaggedObject" (isTaggedObject . gSomeTypeToJSONTaggedObject) + , testProperty "TaggedFlatObject" (isTaggedObject . gSomeTypeToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (isObjectWithSingleField . gSomeTypeToJSONObjectWithSingleField) , testGroup "roundTrip" [ testProperty "2ElemArray" (toParseJSON gSomeTypeParseJSON2ElemArray gSomeTypeToJSON2ElemArray) , testProperty "TaggedObject" (toParseJSON gSomeTypeParseJSONTaggedObject gSomeTypeToJSONTaggedObject) + , testProperty "TaggedFlatObject" (toParseJSON gSomeTypeParseJSONTaggedFlatObject gSomeTypeToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (toParseJSON gSomeTypeParseJSONObjectWithSingleField gSomeTypeToJSONObjectWithSingleField) , testProperty "2ElemArray unary" (toParseJSON1 gSomeTypeLiftParseJSON2ElemArray gSomeTypeLiftToJSON2ElemArray) , testProperty "TaggedObject unary" (toParseJSON1 gSomeTypeLiftParseJSONTaggedObject gSomeTypeLiftToJSONTaggedObject) + , testProperty "TaggedFlatObject unary" (toParseJSON1 gSomeTypeLiftParseJSONTaggedFlatObject gSomeTypeLiftToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField unary" (toParseJSON1 gSomeTypeLiftParseJSONObjectWithSingleField gSomeTypeLiftToJSONObjectWithSingleField) ] ] @@ -70,6 +75,8 @@ genericTests = gNullaryToJSON2ElemArray `sameAs` gNullaryToEncoding2ElemArray , testProperty "NullaryTaggedObject" $ gNullaryToJSONTaggedObject `sameAs` gNullaryToEncodingTaggedObject + , testProperty "NullaryTaggedFlatObject" $ + gNullaryToJSONTaggedFlatObject `sameAs` gNullaryToEncodingTaggedFlatObject , testProperty "NullaryObjectWithSingleField" $ gNullaryToJSONObjectWithSingleField `sameAs` gNullaryToEncodingObjectWithSingleField @@ -94,6 +101,13 @@ genericTests = gSomeTypeLiftToJSONTaggedObject `sameAs1` gSomeTypeLiftToEncodingTaggedObject , testProperty "SomeTypeTaggedObject unary agree" $ gSomeTypeToEncodingTaggedObject `sameAs1Agree` gSomeTypeLiftToEncodingTaggedObject + + , testProperty "SomeTyptTaggedFlatObject" $ + gSomeTypeToJSONTaggedFlatObject `sameAs` gSomeTypeToEncodingTaggedFlatObject + , testProperty "SomeTyptTaggedFlatObject unary" $ + gSomeTypeLiftToJSONTaggedFlatObject `sameAs1` gSomeTypeLiftToEncodingTaggedFlatObject + , testProperty "SomeTyptTaggedFlatObject unary agree" $ + gSomeTypeToEncodingTaggedFlatObject `sameAs1Agree` gSomeTypeLiftToEncodingTaggedFlatObject , testProperty "SomeTypeObjectWithSingleField" $ gSomeTypeToJSONObjectWithSingleField `sameAs` gSomeTypeToEncodingObjectWithSingleField From 6bccf140e323d6d8f5351391291bfc5c5b89e5d3 Mon Sep 17 00:00:00 2001 From: Poscat Date: Tue, 19 Jan 2021 14:04:24 +0800 Subject: [PATCH 04/15] Implement deriveFromJSON for TaggedFlatObject --- src/Data/Aeson/TH.hs | 84 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/src/Data/Aeson/TH.hs b/src/Data/Aeson/TH.hs index e62366519..1ce091a25 100644 --- a/src/Data/Aeson/TH.hs +++ b/src/Data/Aeson/TH.hs @@ -763,7 +763,8 @@ consFromJSON jc tName opts instTys cons = do case sumEncoding opts of TaggedObject {tagFieldName, contentsFieldName} -> parseObject $ parseTaggedObject tvMap tagFieldName contentsFieldName - TaggedFlatObject {tagFieldName} -> error "unsupported" + TaggedFlatObject {tagFieldName} -> + parseObject $ parseTaggedFlatObject tvMap tagFieldName UntaggedValue -> error "UntaggedValue: Should be handled already" ObjectWithSingleField -> parseObject $ parseObjectWithSingleField tvMap @@ -814,13 +815,88 @@ consFromJSON jc tName opts instTys cons = do , noBindS $ parseContents tvMap conKey (Left (valFieldName, obj)) 'conNotFoundFailTaggedObject ] - parseTaggedFlatObject tvMap typFieldName obj = do + parseTaggedFlatObject tvMap tagFieldName obj = do conKey <- newName "conKey" doE [ bindS (varP conKey) - (infixApp (varE obj) [|(.:)|] ([|T.pack|] `appE` stringE typFieldName)) - , noBindS $ parseContents tvMap conKey (Right obj) 'conNotFoundFailTaggedObject + (infixApp (varE obj) [|(.:)|] ([|T.pack|] `appE` stringE tagFieldName)) + , noBindS $ + caseE (varE conKey) + [ match wildP + ( guardedB $ + [ do g <- normalG $ infixApp (varE conKey) + [|(==)|] + ([|T.pack|] `appE` + conNameExp opts con) + argTys <- mapM resolveTypeSynonyms (constructorFields con) + let conName = constructorName con + e <- case constructorVariant con of + RecordConstructor fields -> + parseRecord jc tvMap argTys opts tName conName fields obj False + _ -> + parseNumRec tvMap argTys conName obj + return (g, e) + | con <- cons + ] + ++ + [ liftM2 (,) + (normalG [e|otherwise|]) + ( varE 'conNotFoundFailTaggedObject + `appE` litE (stringL $ show tName) + `appE` listE (map ( litE + . stringL + . constructorTagModifier opts + . nameBase + . constructorName + ) cons + ) + `appE` ([|T.unpack|] `appE` varE conKey) + ) + ] + ) + [] + ] ] + parseNumRec :: TyVarMap + -> [Type] + -> Name + -> Name + -> ExpQ + parseNumRec tvMap argTys conName obj = + (if rejectUnknownFields opts + then infixApp checkUnknownRecords [|(>>)|] + else id) $ + if null argTys + then [|pure|] `appE` conE conName + else + foldl' (\a b -> infixApp a [|(<*>)|] b) + (infixApp (conE conName) [|(<$>)|] x) + xs + where + fields = map (show :: Int -> String) $ take (length argTys) [1..] + knownFields = appE [|H.fromList|] $ listE $ + map (\knownName -> tupE [appE [|T.pack|] $ litE $ stringL knownName, [|()|]]) fields + checkUnknownRecords = + caseE (appE [|H.keys|] $ infixApp (varE obj) [|H.difference|] knownFields) + [ match (listP []) (normalB [|return ()|]) [] + , newName "unknownFields" >>= + \unknownFields -> match (varP unknownFields) + (normalB $ appE [|fail|] $ infixApp + (litE (stringL "Unknown fields: ")) + [|(++)|] + (appE [|show|] (varE unknownFields))) + [] + ] + x:xs = [ [|lookupField|] + `appE` dispatchParseJSON jc conName tvMap argTy + `appE` litE (stringL $ show tName) + `appE` litE (stringL $ constructorTagModifier opts $ nameBase conName) + `appE` varE obj + `appE` ( [|T.pack|] `appE` stringE field + ) + | (field, argTy) <- zip fields argTys + ] + parseUntaggedValue tvMap cons' conVal = foldr1 (\e e' -> infixApp e [|(<|>)|] e') (map (\x -> parseValue tvMap x conVal) cons') From b88c3783ed2fb0e706b95aae93d1a6fc2ddf5e3c Mon Sep 17 00:00:00 2001 From: Poscat Date: Tue, 19 Jan 2021 14:05:03 +0800 Subject: [PATCH 05/15] Add th property tests for TaggedFlatObject --- tests/Encoders.hs | 29 +++++++++++++++++++++++++++++ tests/PropertyTH.hs | 14 ++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/tests/Encoders.hs b/tests/Encoders.hs index 8584da18d..6260a2425 100644 --- a/tests/Encoders.hs +++ b/tests/Encoders.hs @@ -47,6 +47,16 @@ thNullaryParseJSONTaggedObject :: Value -> Parser Nullary thNullaryParseJSONTaggedObject = $(mkParseJSON optsTaggedObject ''Nullary) +thNullaryToJSONTaggedFlatObject :: Nullary -> Value +thNullaryToJSONTaggedFlatObject = $(mkToJSON optsTaggedFlatObject ''Nullary) + +thNullaryToEncodingTaggedFlatObject :: Nullary -> Encoding +thNullaryToEncodingTaggedFlatObject = $(mkToEncoding optsTaggedFlatObject ''Nullary) + +thNullaryParseJSONTaggedFlatObject :: Value -> Parser Nullary +thNullaryParseJSONTaggedFlatObject = $(mkParseJSON optsTaggedFlatObject ''Nullary) + + thNullaryToJSONObjectWithSingleField :: Nullary -> Value thNullaryToJSONObjectWithSingleField = $(mkToJSON optsObjectWithSingleField ''Nullary) @@ -170,6 +180,25 @@ thSomeTypeLiftParseJSONTaggedObject :: LiftParseJSON SomeType a thSomeTypeLiftParseJSONTaggedObject = $(mkLiftParseJSON optsTaggedObject ''SomeType) +thSomeTypeToJSONTaggedFlatObject :: SomeType Int -> Value +thSomeTypeToJSONTaggedFlatObject = $(mkToJSON optsTaggedFlatObject ''SomeType) + +thSomeTypeToEncodingTaggedFlatObject :: SomeType Int -> Encoding +thSomeTypeToEncodingTaggedFlatObject = $(mkToEncoding optsTaggedFlatObject ''SomeType) + +thSomeTypeLiftToJSONTaggedFlatObject :: LiftToJSON SomeType a +thSomeTypeLiftToJSONTaggedFlatObject = $(mkLiftToJSON optsTaggedFlatObject ''SomeType) + +thSomeTypeLiftToEncodingTaggedFlatObject :: LiftToEncoding SomeType a +thSomeTypeLiftToEncodingTaggedFlatObject = $(mkLiftToEncoding optsTaggedFlatObject ''SomeType) + +thSomeTypeParseJSONTaggedFlatObject :: Value -> Parser (SomeType Int) +thSomeTypeParseJSONTaggedFlatObject = $(mkParseJSON optsTaggedFlatObject ''SomeType) + +thSomeTypeLiftParseJSONTaggedFlatObject :: LiftParseJSON SomeType a +thSomeTypeLiftParseJSONTaggedFlatObject = $(mkLiftParseJSON optsTaggedFlatObject ''SomeType) + + thSomeTypeToJSONObjectWithSingleField :: SomeType Int -> Value thSomeTypeToJSONObjectWithSingleField = $(mkToJSON optsObjectWithSingleField ''SomeType) diff --git a/tests/PropertyTH.hs b/tests/PropertyTH.hs index e4ef0b391..6e2a91c67 100644 --- a/tests/PropertyTH.hs +++ b/tests/PropertyTH.hs @@ -22,12 +22,14 @@ templateHaskellTests = testProperty "string" (isString . thNullaryToJSONString) , testProperty "2ElemArray" (is2ElemArray . thNullaryToJSON2ElemArray) , testProperty "TaggedObject" (isNullaryTaggedObject . thNullaryToJSONTaggedObject) + , testProperty "TaggedFlatObject" (isNullaryTaggedObject . thNullaryToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thNullaryToJSONObjectWithSingleField) , testGroup "roundTrip" [ testProperty "string" (toParseJSON thNullaryParseJSONString thNullaryToJSONString) , testProperty "2ElemArray" (toParseJSON thNullaryParseJSON2ElemArray thNullaryToJSON2ElemArray) , testProperty "TaggedObject" (toParseJSON thNullaryParseJSONTaggedObject thNullaryToJSONTaggedObject) + , testProperty "TaggedFlatObject" (toParseJSON thNullaryParseJSONTaggedFlatObject thNullaryToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (toParseJSON thNullaryParseJSONObjectWithSingleField thNullaryToJSONObjectWithSingleField) ] ] @@ -38,14 +40,17 @@ templateHaskellTests = , testGroup "SomeType" [ testProperty "2ElemArray" (is2ElemArray . thSomeTypeToJSON2ElemArray) , testProperty "TaggedObject" (isTaggedObject . thSomeTypeToJSONTaggedObject) + , testProperty "TaggedFlatObject" (isTaggedObject . thSomeTypeToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thSomeTypeToJSONObjectWithSingleField) , testGroup "roundTrip" [ testProperty "2ElemArray" (toParseJSON thSomeTypeParseJSON2ElemArray thSomeTypeToJSON2ElemArray) , testProperty "TaggedObject" (toParseJSON thSomeTypeParseJSONTaggedObject thSomeTypeToJSONTaggedObject) + , testProperty "TaggedFlatObject" (toParseJSON thSomeTypeParseJSONTaggedFlatObject thSomeTypeToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (toParseJSON thSomeTypeParseJSONObjectWithSingleField thSomeTypeToJSONObjectWithSingleField) , testProperty "2ElemArray unary" (toParseJSON1 thSomeTypeLiftParseJSON2ElemArray thSomeTypeLiftToJSON2ElemArray) , testProperty "TaggedObject unary" (toParseJSON1 thSomeTypeLiftParseJSONTaggedObject thSomeTypeLiftToJSONTaggedObject) + , testProperty "TaggedFlatObject unary" (toParseJSON1 thSomeTypeLiftParseJSONTaggedFlatObject thSomeTypeLiftToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField unary" (toParseJSON1 thSomeTypeLiftParseJSONObjectWithSingleField thSomeTypeLiftToJSONObjectWithSingleField) ] @@ -87,6 +92,8 @@ templateHaskellTests = thNullaryToJSON2ElemArray `sameAs` thNullaryToEncoding2ElemArray , testProperty "NullaryTaggedObject" $ thNullaryToJSONTaggedObject `sameAs` thNullaryToEncodingTaggedObject + , testProperty "NullaryTaggedFlatObject" $ + thNullaryToJSONTaggedFlatObject `sameAs` thNullaryToEncodingTaggedFlatObject , testProperty "NullaryObjectWithSingleField" $ thNullaryToJSONObjectWithSingleField `sameAs` thNullaryToEncodingObjectWithSingleField @@ -112,6 +119,13 @@ templateHaskellTests = , testProperty "SomeTypeTaggedObject unary agree" $ thSomeTypeToEncodingTaggedObject `sameAs1Agree` thSomeTypeLiftToEncodingTaggedObject + , testProperty "SomeTypeTaggedFlatObject" $ + thSomeTypeToJSONTaggedFlatObject `sameAs` thSomeTypeToEncodingTaggedFlatObject + , testProperty "SomeTypeTaggedFlatObject unary" $ + thSomeTypeLiftToJSONTaggedFlatObject `sameAs1` thSomeTypeLiftToEncodingTaggedFlatObject + , testProperty "SomeTypeTaggedFlatObject unary agree" $ + thSomeTypeToEncodingTaggedFlatObject `sameAs1Agree` thSomeTypeLiftToEncodingTaggedFlatObject + , testProperty "SomeTypeObjectWithSingleField" $ thSomeTypeToJSONObjectWithSingleField `sameAs` thSomeTypeToEncodingObjectWithSingleField , testProperty "SomeTypeObjectWithSingleField unary" $ From 6b64f1f06560da7fed90cfa11dd3d3b63e62fc8b Mon Sep 17 00:00:00 2001 From: Poscat Date: Tue, 19 Jan 2021 14:51:15 +0800 Subject: [PATCH 06/15] More property tests --- tests/DataFamilies/Encoders.hs | 40 ++++++++++++++++++++++++++++++++ tests/DataFamilies/Properties.hs | 16 +++++++++++++ 2 files changed, 56 insertions(+) diff --git a/tests/DataFamilies/Encoders.hs b/tests/DataFamilies/Encoders.hs index 111050b95..59c4a79a3 100644 --- a/tests/DataFamilies/Encoders.hs +++ b/tests/DataFamilies/Encoders.hs @@ -46,6 +46,16 @@ thNullaryParseJSONTaggedObject :: Value -> Parser (Nullary Int) thNullaryParseJSONTaggedObject = $(mkParseJSON optsTaggedObject 'C3) +thNullaryToJSONTaggedFlatObject :: Nullary Int -> Value +thNullaryToJSONTaggedFlatObject = $(mkToJSON optsTaggedFlatObject 'C1) + +thNullaryToEncodingTaggedFlatObject :: Nullary Int -> Encoding +thNullaryToEncodingTaggedFlatObject = $(mkToEncoding optsTaggedFlatObject 'C2) + +thNullaryParseJSONTaggedFlatObject :: Value -> Parser (Nullary Int) +thNullaryParseJSONTaggedFlatObject = $(mkParseJSON optsTaggedFlatObject 'C3) + + thNullaryToJSONObjectWithSingleField :: Nullary Int -> Value thNullaryToJSONObjectWithSingleField = $(mkToJSON optsObjectWithSingleField 'C1) @@ -82,6 +92,16 @@ thSomeTypeParseJSONTaggedObject :: Value -> Parser (SomeType c () Int) thSomeTypeParseJSONTaggedObject = $(mkParseJSON optsTaggedObject 'Unary) +thSomeTypeToJSONTaggedFlatObject :: SomeType c () Int -> Value +thSomeTypeToJSONTaggedFlatObject = $(mkToJSON optsTaggedFlatObject 'Record) + +thSomeTypeToEncodingTaggedFlatObject :: SomeType c () Int -> Encoding +thSomeTypeToEncodingTaggedFlatObject = $(mkToEncoding optsTaggedFlatObject 'Nullary) + +thSomeTypeParseJSONTaggedFlatObject :: Value -> Parser (SomeType c () Int) +thSomeTypeParseJSONTaggedFlatObject = $(mkParseJSON optsTaggedFlatObject 'Unary) + + thSomeTypeToJSONObjectWithSingleField :: SomeType c () Int -> Value thSomeTypeToJSONObjectWithSingleField = $(mkToJSON optsObjectWithSingleField 'Product) @@ -177,6 +197,16 @@ gNullaryParseJSONTaggedObject :: Value -> Parser (Nullary Int) gNullaryParseJSONTaggedObject = genericParseJSON optsTaggedObject +gNullaryToJSONTaggedFlatObject :: Nullary Int -> Value +gNullaryToJSONTaggedFlatObject = genericToJSON optsTaggedFlatObject + +gNullaryToEncodingTaggedFlatObject :: Nullary Int -> Encoding +gNullaryToEncodingTaggedFlatObject = genericToEncoding optsTaggedFlatObject + +gNullaryParseJSONTaggedFlatObject :: Value -> Parser (Nullary Int) +gNullaryParseJSONTaggedFlatObject = genericParseJSON optsTaggedFlatObject + + gNullaryToJSONObjectWithSingleField :: Nullary Int -> Value gNullaryToJSONObjectWithSingleField = genericToJSON optsObjectWithSingleField @@ -208,6 +238,16 @@ gSomeTypeParseJSONTaggedObject :: Value -> Parser (SomeType c () Int) gSomeTypeParseJSONTaggedObject = genericParseJSON optsTaggedObject +gSomeTypeToJSONTaggedFlatObject :: SomeType c () Int -> Value +gSomeTypeToJSONTaggedFlatObject = genericToJSON optsTaggedFlatObject + +gSomeTypeToEncodingTaggedFlatObject :: SomeType c () Int -> Encoding +gSomeTypeToEncodingTaggedFlatObject = genericToEncoding optsTaggedFlatObject + +gSomeTypeParseJSONTaggedFlatObject :: Value -> Parser (SomeType c () Int) +gSomeTypeParseJSONTaggedFlatObject = genericParseJSON optsTaggedFlatObject + + gSomeTypeToJSONObjectWithSingleField :: SomeType c () Int -> Value gSomeTypeToJSONObjectWithSingleField = genericToJSON optsObjectWithSingleField diff --git a/tests/DataFamilies/Properties.hs b/tests/DataFamilies/Properties.hs index 67492ed67..b0c43b9a1 100644 --- a/tests/DataFamilies/Properties.hs +++ b/tests/DataFamilies/Properties.hs @@ -23,21 +23,25 @@ tests = testGroup "data families" [ testProperty "string" (isString . thNullaryToJSONString) , testProperty "2ElemArray" (is2ElemArray . thNullaryToJSON2ElemArray) , testProperty "TaggedObject" (isNullaryTaggedObject . thNullaryToJSONTaggedObject) + , testProperty "TaggedFlatObject" (isNullaryTaggedObject . thNullaryToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thNullaryToJSONObjectWithSingleField) , testGroup "roundTrip" [ testProperty "string" (toParseJSON thNullaryParseJSONString thNullaryToJSONString) , testProperty "2ElemArray" (toParseJSON thNullaryParseJSON2ElemArray thNullaryToJSON2ElemArray) , testProperty "TaggedObject" (toParseJSON thNullaryParseJSONTaggedObject thNullaryToJSONTaggedObject) + , testProperty "TaggedFlatObject" (toParseJSON thNullaryParseJSONTaggedFlatObject thNullaryToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (toParseJSON thNullaryParseJSONObjectWithSingleField thNullaryToJSONObjectWithSingleField) ] ] , testGroup "SomeType" [ testProperty "2ElemArray" (is2ElemArray . thSomeTypeToJSON2ElemArray) , testProperty "TaggedObject" (isTaggedObject . thSomeTypeToJSONTaggedObject) + , testProperty "TaggedFlatObject" (isTaggedObject . thSomeTypeToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (isObjectWithSingleField . thSomeTypeToJSONObjectWithSingleField) , testGroup "roundTrip" [ testProperty "2ElemArray" (toParseJSON thSomeTypeParseJSON2ElemArray thSomeTypeToJSON2ElemArray) , testProperty "TaggedObject" (toParseJSON thSomeTypeParseJSONTaggedObject thSomeTypeToJSONTaggedObject) + , testProperty "TaggedFlatObject" (toParseJSON thSomeTypeParseJSONTaggedFlatObject thSomeTypeToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (toParseJSON thSomeTypeParseJSONObjectWithSingleField thSomeTypeToJSONObjectWithSingleField) ] ] @@ -65,6 +69,8 @@ tests = testGroup "data families" [ thNullaryToJSON2ElemArray `sameAs` thNullaryToEncoding2ElemArray , testProperty "NullaryTaggedObject" $ thNullaryToJSONTaggedObject `sameAs` thNullaryToEncodingTaggedObject + , testProperty "NullaryTaggedFlatObject" $ + thNullaryToJSONTaggedFlatObject `sameAs` thNullaryToEncodingTaggedFlatObject , testProperty "NullaryObjectWithSingleField" $ thNullaryToJSONObjectWithSingleField `sameAs` thNullaryToEncodingObjectWithSingleField @@ -76,6 +82,8 @@ tests = testGroup "data families" [ thSomeTypeToJSON2ElemArray `sameAs` thSomeTypeToEncoding2ElemArray , testProperty "SomeTypeTaggedObject" $ thSomeTypeToJSONTaggedObject `sameAs` thSomeTypeToEncodingTaggedObject + , testProperty "SomeTypeTaggedFlatObject" $ + thSomeTypeToJSONTaggedFlatObject `sameAs` thSomeTypeToEncodingTaggedFlatObject , testProperty "SomeTypeObjectWithSingleField" $ thSomeTypeToJSONObjectWithSingleField `sameAs` thSomeTypeToEncodingObjectWithSingleField @@ -88,21 +96,25 @@ tests = testGroup "data families" [ testProperty "string" (isString . gNullaryToJSONString) , testProperty "2ElemArray" (is2ElemArray . gNullaryToJSON2ElemArray) , testProperty "TaggedObject" (isNullaryTaggedObject . gNullaryToJSONTaggedObject) + , testProperty "TaggedFlatObject" (isNullaryTaggedObject . gNullaryToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (isObjectWithSingleField . gNullaryToJSONObjectWithSingleField) , testGroup "roundTrip" [ testProperty "string" (toParseJSON gNullaryParseJSONString gNullaryToJSONString) , testProperty "2ElemArray" (toParseJSON gNullaryParseJSON2ElemArray gNullaryToJSON2ElemArray) , testProperty "TaggedObject" (toParseJSON gNullaryParseJSONTaggedObject gNullaryToJSONTaggedObject) + , testProperty "TaggedFlatObject" (toParseJSON gNullaryParseJSONTaggedFlatObject gNullaryToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (toParseJSON gNullaryParseJSONObjectWithSingleField gNullaryToJSONObjectWithSingleField) ] ] , testGroup "SomeType" [ testProperty "2ElemArray" (is2ElemArray . gSomeTypeToJSON2ElemArray) , testProperty "TaggedObject" (isTaggedObject . gSomeTypeToJSONTaggedObject) + , testProperty "TaggedFlatObject" (isTaggedObject . gSomeTypeToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (isObjectWithSingleField . gSomeTypeToJSONObjectWithSingleField) , testGroup "roundTrip" [ testProperty "2ElemArray" (toParseJSON gSomeTypeParseJSON2ElemArray gSomeTypeToJSON2ElemArray) , testProperty "TaggedObject" (toParseJSON gSomeTypeParseJSONTaggedObject gSomeTypeToJSONTaggedObject) + , testProperty "TaggedFlatObject" (toParseJSON gSomeTypeParseJSONTaggedFlatObject gSomeTypeToJSONTaggedFlatObject) , testProperty "ObjectWithSingleField" (toParseJSON gSomeTypeParseJSONObjectWithSingleField gSomeTypeToJSONObjectWithSingleField) ] ] @@ -122,6 +134,8 @@ tests = testGroup "data families" [ gNullaryToJSON2ElemArray `sameAs` gNullaryToEncoding2ElemArray , testProperty "NullaryTaggedObject" $ gNullaryToJSONTaggedObject `sameAs` gNullaryToEncodingTaggedObject + , testProperty "NullaryTaggedFlatObject" $ + gNullaryToJSONTaggedFlatObject `sameAs` gNullaryToEncodingTaggedFlatObject , testProperty "NullaryObjectWithSingleField" $ gNullaryToJSONObjectWithSingleField `sameAs` gNullaryToEncodingObjectWithSingleField @@ -133,6 +147,8 @@ tests = testGroup "data families" [ gSomeTypeToJSON2ElemArray `sameAs` gSomeTypeToEncoding2ElemArray , testProperty "SomeTypeTaggedObject" $ gSomeTypeToJSONTaggedObject `sameAs` gSomeTypeToEncodingTaggedObject + , testProperty "SomeTypeTaggedFlatObject" $ + gSomeTypeToJSONTaggedFlatObject `sameAs` gSomeTypeToEncodingTaggedFlatObject , testProperty "SomeTypeObjectWithSingleField" $ gSomeTypeToJSONObjectWithSingleField `sameAs` gSomeTypeToEncodingObjectWithSingleField From bac6f3a1380dce432ece6879a005eaf76287f47d Mon Sep 17 00:00:00 2001 From: Poscat Date: Tue, 19 Jan 2021 14:51:58 +0800 Subject: [PATCH 07/15] Unit tests for TaggedFlatObject --- tests/UnitTests/NullaryConstructors.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/UnitTests/NullaryConstructors.hs b/tests/UnitTests/NullaryConstructors.hs index dcc19cace..8d1e7cc5d 100644 --- a/tests/UnitTests/NullaryConstructors.hs +++ b/tests/UnitTests/NullaryConstructors.hs @@ -31,6 +31,8 @@ nullaryConstructors = , dec "[\"c1\",[]]" @=? thNullaryToJSON2ElemArray C1 , dec "{\"tag\":\"c1\"}" @=? thNullaryToJSONTaggedObject C1 , dec "{\"tag\":\"c1\"}" @=? gNullaryToJSONTaggedObject C1 + , dec "{\"tag\":\"c1\"}" @=? thNullaryToJSONTaggedFlatObject C1 + , dec "{\"tag\":\"c1\"}" @=? gNullaryToJSONTaggedFlatObject C1 , decE "\"C1\"" @=? enc (gNullaryToEncodingString C1) , decE "\"C1\"" @=? enc (thNullaryToEncodingString C1) @@ -40,6 +42,8 @@ nullaryConstructors = , decE "{\"c1\":[]}" @=? enc (gNullaryToEncodingObjectWithSingleField C1) , decE "{\"tag\":\"c1\"}" @=? enc (thNullaryToEncodingTaggedObject C1) , decE "{\"tag\":\"c1\"}" @=? enc (gNullaryToEncodingTaggedObject C1) + , decE "{\"tag\":\"c1\"}" @=? enc (thNullaryToEncodingTaggedFlatObject C1) + , decE "{\"tag\":\"c1\"}" @=? enc (gNullaryToEncodingTaggedFlatObject C1) , ISuccess C1 @=? parse thNullaryParseJSONTaggedObject (dec "{\"tag\":\"c1\"}") , ISuccess C1 @=? parse gNullaryParseJSONTaggedObject (dec "{\"tag\":\"c1\"}") From 332a05e6a790cae63e4ca59b8200528023db33ff Mon Sep 17 00:00:00 2001 From: Poscat Date: Tue, 19 Jan 2021 23:05:07 +0800 Subject: [PATCH 08/15] Fix compatibility with older ghc versions --- src/Data/Aeson/Types/FromJSON.hs | 20 +++++++++++--------- src/Data/Aeson/Types/ToJSON.hs | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Data/Aeson/Types/FromJSON.hs b/src/Data/Aeson/Types/FromJSON.hs index b4c65fe78..72975af40 100644 --- a/src/Data/Aeson/Types/FromJSON.hs +++ b/src/Data/Aeson/Types/FromJSON.hs @@ -4,13 +4,13 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE UndecidableInstances #-} @@ -159,8 +159,7 @@ import qualified Data.Primitive.Types as PM import qualified Data.Primitive.PrimArray as PM import Data.Coerce (Coercible, coerce) -import GHC.TypeNats -import Data.Kind (Type) +import GHC.TypeLits parseIndexedJSON :: (Value -> Parser a) -> Int -> Value -> Parser a parseIndexedJSON p idx value = p value Index idx @@ -1434,12 +1433,15 @@ instance ( IsRecord f isRecord , FromTaggedFlatObject' arity f isRecord , Constructor c ) => FromTaggedFlatObject arity (C1 c f) where + parseTaggedFlatObject :: Text :* TypeName :* Options :* FromArgs arity a + -> Object + -> Maybe (Parser (C1 c f a)) parseTaggedFlatObject (tag :* p@(_ :* opts :* _)) obj - | tag == tag' = Just $ fmap M1 $ (unTagged @Type @isRecord) $ parseTaggedFlatObject' (cname :* p) obj + | tag == tag' = Just $ fmap M1 $ (unTagged :: Tagged isRecord (Parser (f a)) -> Parser (f a)) $ parseTaggedFlatObject' (cname :* p) obj | otherwise = Nothing where tag' = pack $ constructorTagModifier opts cname - cname = conName (undefined :: M1 i c a p) + cname = conName (undefined :: M1 i c f p) class FromTaggedFlatObject' arity f isRecord where parseTaggedFlatObject' :: ConName :* TypeName :* Options :* FromArgs arity a @@ -1453,7 +1455,7 @@ instance FromTaggedFlatObject' arity U1 False where parseTaggedFlatObject' _ _ = Tagged (pure U1) instance OVERLAPPABLE_ (PositionFromObject 1 arity f) => FromTaggedFlatObject' arity f False where - parseTaggedFlatObject' (_ :* p) obj = Tagged (positionFromObject (Proxy @1) p obj) + parseTaggedFlatObject' (_ :* p) obj = Tagged (positionFromObject (Proxy :: Proxy 1) p obj) class KnownNat n => PositionFromObject n arity f where positionFromObject :: Proxy n @@ -1463,15 +1465,15 @@ class KnownNat n => PositionFromObject n arity f where instance (KnownNat n, GFromJSON arity a) => PositionFromObject n arity (S1 m a) where positionFromObject _ (_ :* opts :* fargs) obj = - explicitParseField (gParseJSON opts fargs) obj $ pack $ show $ natVal $ Proxy @n + explicitParseField (gParseJSON opts fargs) obj $ pack $ show $ natVal (Proxy :: Proxy n) instance ( PositionFromObject n arity f , PositionFromObject (n+1) arity g ) => PositionFromObject n arity (f :*: g) where positionFromObject _ p obj = (:*:) - <$> positionFromObject (Proxy @n) p obj - <*> positionFromObject (Proxy @(n+1)) p obj + <$> positionFromObject (Proxy :: Proxy n) p obj + <*> positionFromObject (Proxy :: Proxy (n+1)) p obj -------------------------------------------------------------------------------- diff --git a/src/Data/Aeson/Types/ToJSON.hs b/src/Data/Aeson/Types/ToJSON.hs index bf791e74e..5f9c09d69 100644 --- a/src/Data/Aeson/Types/ToJSON.hs +++ b/src/Data/Aeson/Types/ToJSON.hs @@ -142,7 +142,7 @@ import qualified Data.Primitive.Array as PM import qualified Data.Primitive.SmallArray as PM import qualified Data.Primitive.Types as PM import qualified Data.Primitive.PrimArray as PM -import GHC.TypeNats +import GHC.TypeLits toJSONPair :: (a -> Value) -> (b -> Value) -> (a, b) -> Value toJSONPair a b = liftToJSON2 a (listValue a) b (listValue b) From 46c76f65fdadc639951df2502bc026ff2b10bdd7 Mon Sep 17 00:00:00 2001 From: Poscat Date: Fri, 22 Jan 2021 20:17:21 +0800 Subject: [PATCH 09/15] improve formatting --- src/Data/Aeson/Types/FromJSON.hs | 2 +- src/Data/Aeson/Types/ToJSON.hs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Data/Aeson/Types/FromJSON.hs b/src/Data/Aeson/Types/FromJSON.hs index 72975af40..b1cc5bde6 100644 --- a/src/Data/Aeson/Types/FromJSON.hs +++ b/src/Data/Aeson/Types/FromJSON.hs @@ -1454,7 +1454,7 @@ instance (RecordFromJSON arity f, FieldNames f) => FromTaggedFlatObject' arity f instance FromTaggedFlatObject' arity U1 False where parseTaggedFlatObject' _ _ = Tagged (pure U1) -instance OVERLAPPABLE_ (PositionFromObject 1 arity f) => FromTaggedFlatObject' arity f False where +instance PositionFromObject 1 arity f => FromTaggedFlatObject' arity f False where parseTaggedFlatObject' (_ :* p) obj = Tagged (positionFromObject (Proxy :: Proxy 1) p obj) class KnownNat n => PositionFromObject n arity f where diff --git a/src/Data/Aeson/Types/ToJSON.hs b/src/Data/Aeson/Types/ToJSON.hs index 5f9c09d69..2fea17c0b 100644 --- a/src/Data/Aeson/Types/ToJSON.hs +++ b/src/Data/Aeson/Types/ToJSON.hs @@ -954,17 +954,23 @@ instance RecordToPairs pairs enc arity f => TaggedFlatObject' pairs enc arity f instance Monoid pairs => TaggedFlatObject' enc pairs arity U1 False where taggedFlatObject' _ _ _ = Tagged mempty -instance OVERLAPPABLE_ (PositionToPairs 1 pairs enc arity f) => TaggedFlatObject' enc pairs arity f False where +instance PositionToPairs 1 pairs enc arity f => TaggedFlatObject' enc pairs arity f False where taggedFlatObject' opts targs a = Tagged $ positionToPairs (Proxy :: Proxy 1) opts targs a class KnownNat n => PositionToPairs n pairs enc arity f where positionToPairs :: Proxy n -> Options -> ToArgs enc arity a -> f a -> pairs -instance (KeyValuePair enc pairs, GToJSON' enc arity a, KnownNat n) => PositionToPairs n pairs enc arity (S1 m a) where +instance ( KeyValuePair enc pairs + , GToJSON' enc arity a + , KnownNat n + ) => PositionToPairs n pairs enc arity (S1 m a) where positionToPairs p opts targs (M1 a) = show (natVal p) `pair` gToJSON opts targs a -instance (Semigroup pairs, PositionToPairs n pairs enc arity f, PositionToPairs (n+1) pairs enc arity g) => PositionToPairs n pairs enc arity (f :*: g) where +instance ( Semigroup pairs + , PositionToPairs n pairs enc arity f + , PositionToPairs (n+1) pairs enc arity g + ) => PositionToPairs n pairs enc arity (f :*: g) where positionToPairs _ opts targs (f :*: g) = positionToPairs (Proxy :: Proxy n) opts targs f <> positionToPairs (Proxy :: Proxy (n+1)) opts targs g From 000c441f8cda26a40d22ad0dbbaffac18eb0e2fa Mon Sep 17 00:00:00 2001 From: Poscat Date: Fri, 22 Jan 2021 22:32:32 +0800 Subject: [PATCH 10/15] fix overlapping instances --- src/Data/Aeson/Types/FromJSON.hs | 2 +- src/Data/Aeson/Types/ToJSON.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/Aeson/Types/FromJSON.hs b/src/Data/Aeson/Types/FromJSON.hs index b1cc5bde6..3a3e57b8d 100644 --- a/src/Data/Aeson/Types/FromJSON.hs +++ b/src/Data/Aeson/Types/FromJSON.hs @@ -1454,7 +1454,7 @@ instance (RecordFromJSON arity f, FieldNames f) => FromTaggedFlatObject' arity f instance FromTaggedFlatObject' arity U1 False where parseTaggedFlatObject' _ _ = Tagged (pure U1) -instance PositionFromObject 1 arity f => FromTaggedFlatObject' arity f False where +instance OVERLAPPABLE_ PositionFromObject 1 arity f => FromTaggedFlatObject' arity f False where parseTaggedFlatObject' (_ :* p) obj = Tagged (positionFromObject (Proxy :: Proxy 1) p obj) class KnownNat n => PositionFromObject n arity f where diff --git a/src/Data/Aeson/Types/ToJSON.hs b/src/Data/Aeson/Types/ToJSON.hs index 2fea17c0b..5ec36d85c 100644 --- a/src/Data/Aeson/Types/ToJSON.hs +++ b/src/Data/Aeson/Types/ToJSON.hs @@ -954,7 +954,7 @@ instance RecordToPairs pairs enc arity f => TaggedFlatObject' pairs enc arity f instance Monoid pairs => TaggedFlatObject' enc pairs arity U1 False where taggedFlatObject' _ _ _ = Tagged mempty -instance PositionToPairs 1 pairs enc arity f => TaggedFlatObject' enc pairs arity f False where +instance OVERLAPPABLE_ PositionToPairs 1 pairs enc arity f => TaggedFlatObject' enc pairs arity f False where taggedFlatObject' opts targs a = Tagged $ positionToPairs (Proxy :: Proxy 1) opts targs a class KnownNat n => PositionToPairs n pairs enc arity f where From d77552b38bb851e882de6b62a09c8eed9504598b Mon Sep 17 00:00:00 2001 From: Poscat Date: Sat, 23 Jan 2021 09:18:46 +0800 Subject: [PATCH 11/15] fix semigroup instance --- src/Data/Aeson/Types/ToJSON.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/Aeson/Types/ToJSON.hs b/src/Data/Aeson/Types/ToJSON.hs index 5ec36d85c..762daf06d 100644 --- a/src/Data/Aeson/Types/ToJSON.hs +++ b/src/Data/Aeson/Types/ToJSON.hs @@ -967,13 +967,13 @@ instance ( KeyValuePair enc pairs positionToPairs p opts targs (M1 a) = show (natVal p) `pair` gToJSON opts targs a -instance ( Semigroup pairs +instance ( Monoid pairs , PositionToPairs n pairs enc arity f , PositionToPairs (n+1) pairs enc arity g ) => PositionToPairs n pairs enc arity (f :*: g) where positionToPairs _ opts targs (f :*: g) = positionToPairs (Proxy :: Proxy n) opts targs f - <> positionToPairs (Proxy :: Proxy (n+1)) opts targs g + `mappend` positionToPairs (Proxy :: Proxy (n+1)) opts targs g -------------------------------------------------------------------------------- From 13f0e48b9a0f2e4d228e8e6ba9025ef83b4aff54 Mon Sep 17 00:00:00 2001 From: Poscat Date: Fri, 29 Jan 2021 06:23:42 +0800 Subject: [PATCH 12/15] Fix semigroup --- flake.nix | 31 +++++++++++++++++++++++++++++++ src/Data/Aeson/TH.hs | 4 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 flake.nix diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..6f92fda56 --- /dev/null +++ b/flake.nix @@ -0,0 +1,31 @@ +{ + inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable; + inputs.flake-utils.url = github:poscat0x04/flake-utils; + + outputs = { self, nixpkgs, flake-utils, ... }: with flake-utils; + eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { inherit system; overlays = [ self.overlay ]; }; + in + with pkgs; + { + devShell = aeson-dev.envFunc { withHoogle = true; }; + defaultPackage = aeson; + } + ) // { + overlay = self: super: + let + hpkgs = super.haskellPackages; + aeson = hpkgs.callCabal2nix "aeson" ./. {}; + in + with super; with haskell.lib; + { + inherit aeson; + aeson-dev = addBuildTools aeson [ + haskell-language-server + cabal-install + ]; + }; + }; +} diff --git a/src/Data/Aeson/TH.hs b/src/Data/Aeson/TH.hs index 1ce091a25..827fa5769 100644 --- a/src/Data/Aeson/TH.hs +++ b/src/Data/Aeson/TH.hs @@ -510,7 +510,7 @@ argsToValue target jc tvMap opts multiCons () | TaggedFlatObject {tagFieldName} <- sumEncoding opts -> do let tag = pairE tagFieldName (conStr target opts conName) - fromPairsE $ infixApp tag [|(<>)|] pairs + fromPairsE $ infixApp tag [|(Monoid.<>)|] pairs | otherwise -> recordSumToValue target opts multiCons (null argTys) conName pairs) [] @@ -763,7 +763,7 @@ consFromJSON jc tName opts instTys cons = do case sumEncoding opts of TaggedObject {tagFieldName, contentsFieldName} -> parseObject $ parseTaggedObject tvMap tagFieldName contentsFieldName - TaggedFlatObject {tagFieldName} -> + TaggedFlatObject {tagFieldName} -> parseObject $ parseTaggedFlatObject tvMap tagFieldName UntaggedValue -> error "UntaggedValue: Should be handled already" ObjectWithSingleField -> From 57f92e2822469014c128f1eb0821fa477a303424 Mon Sep 17 00:00:00 2001 From: Poscat Date: Tue, 16 Feb 2021 11:09:49 +0800 Subject: [PATCH 13/15] Clarify the reason why the case is unreachable --- src/Data/Aeson/TH.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Data/Aeson/TH.hs b/src/Data/Aeson/TH.hs index 827fa5769..a6abb22e8 100644 --- a/src/Data/Aeson/TH.hs +++ b/src/Data/Aeson/TH.hs @@ -421,7 +421,7 @@ sumToValue target opts multiCons nullary conName value pairs content = pairs contentsFieldName in fromPairsE $ if nullary then tag else infixApp tag [|(Monoid.<>)|] content - TaggedFlatObject {} -> error "impossible" + TaggedFlatObject {} -> error "TaggedFlatObject: Should be handled already" ObjectWithSingleField -> objectE [(conString opts conName, value)] UntaggedValue | nullary -> conStr target opts conName From 8f92dbf212a35c80855f33d87456edf0559d17bd Mon Sep 17 00:00:00 2001 From: Poscat Date: Thu, 18 Feb 2021 15:40:58 +0800 Subject: [PATCH 14/15] Clarify what the TaggedFlatObject option does --- src/Data/Aeson/Types/Internal.hs | 33 +++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Data/Aeson/Types/Internal.hs b/src/Data/Aeson/Types/Internal.hs index d2f15e45e..a5bf84801 100644 --- a/src/Data/Aeson/Types/Internal.hs +++ b/src/Data/Aeson/Types/Internal.hs @@ -683,7 +683,38 @@ data SumEncoding = -- record the encoded constructor contents will be stored under -- the 'contentsFieldName' field. | TaggedFlatObject { tagFieldName :: String } - -- ^ A constructor will be encoded to an object with a field + -- ^ Conceptually, this option will allow data types to be encoded to an object + -- with an additional field 'tagFieldName' which specifies the constructor tag. + -- This option differs from 'TaggedObject' in that the fields are encoded + -- in the same object as the tag, instead of in another object under the + -- field @contentsFieldName@. + -- + -- The detailed behavior is as follows: + -- + -- 1. If the data type has only a single constructor and has field names + -- (a record), it will be encoded as an object without any additional fields. + -- For example, given @A@ defined as + -- @data A = A {field1 :: Int, field2 :: Int}@, + -- this option will encode @A 1 2@ as @{"field1": 1, "field2": 2}@ + -- 2. If the data type has only a single constructor but does not have any fields, + -- it will be encoded as an array. + -- For example, given @A@ defined as + -- @data A = A Int Int@, + -- this option will encode @A 1 2@ as @[1, 2]@ + -- 3. If the data type has multiple constructors and the constructor has field names, + -- it will be encoded as an object with an additional field '$tagFieldName'. + -- For example, given @A@ defined as + -- @data A = A {field1 :: Int, field2 :: Int} | B@, + -- this option will encode @A 1 2@ as @{"field1": 1, "field2": 2, "$tagFieldName": \"A"}@ + -- 4. If the data type has multiple constructors and the constructor does not have + -- any feild names, it will be encoded as an object whose keys are the position of the value + -- in that data type with an additional field '$tagFieldName'. + -- For example, given @A@ defined as + -- @data A = A Int Int | B@, + -- this option will encode @A 1 2@ as @{"1": 1, "2": 2, "$tagFieldName": \"A"}@ + -- 5. The behavior is undefined when the '$tagFieldName' collides with another field name and should + -- not be relied upon. It may or may not overwite the field. + -- It may or may not throw an runtime exception. It may or may not raise an compile time error. | UntaggedValue -- ^ Constructor names won't be encoded. Instead only the contents of the -- constructor will be encoded as if the type had a single constructor. JSON From 11cc9bed37d51af449425d22315fa8ac1e821b43 Mon Sep 17 00:00:00 2001 From: Poscat Date: Thu, 18 Feb 2021 16:45:57 +0800 Subject: [PATCH 15/15] Make indicies start from 0 --- src/Data/Aeson/TH.hs | 6 +++--- src/Data/Aeson/Types/FromJSON.hs | 10 +++++----- src/Data/Aeson/Types/Internal.hs | 2 +- src/Data/Aeson/Types/ToJSON.hs | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Data/Aeson/TH.hs b/src/Data/Aeson/TH.hs index a6abb22e8..775b48020 100644 --- a/src/Data/Aeson/TH.hs +++ b/src/Data/Aeson/TH.hs @@ -443,7 +443,7 @@ argsToValue target jc tvMap opts multiCons let len = length argTys' args <- newNameList "arg" len let os = zipWith (\arg argTy -> dispatchToJSON target jc conName tvMap argTy `appE` varE arg) args argTys' - pairs = zip (fmap (show :: Int -> String) [1..]) os + pairs = zip (fmap (show :: Int -> String) [0..]) os obj = objectE (tag : pairs) match (conP conName $ map varP args) (normalB obj) @@ -526,7 +526,7 @@ argsToValue target jc tvMap opts multiCons ar <- newName "argR" let tag = (tagFieldName, conStr target opts conName) os = zipWith (\arg argTy -> dispatchToJSON target jc conName tvMap argTy `appE` varE arg) [al, ar] [alTy, arTy] - pairs = zip (fmap (show :: Int -> String) [1..]) os + pairs = zip (fmap (show :: Int -> String) [0..]) os obj = objectE (tag : pairs) match (infixP (varP al) conName (varP ar)) (normalB obj) @@ -873,7 +873,7 @@ consFromJSON jc tName opts instTys cons = do (infixApp (conE conName) [|(<$>)|] x) xs where - fields = map (show :: Int -> String) $ take (length argTys) [1..] + fields = map (show :: Int -> String) $ take (length argTys) [0..] knownFields = appE [|H.fromList|] $ listE $ map (\knownName -> tupE [appE [|T.pack|] $ litE $ stringL knownName, [|()|]]) fields checkUnknownRecords = diff --git a/src/Data/Aeson/Types/FromJSON.hs b/src/Data/Aeson/Types/FromJSON.hs index 3a3e57b8d..1629fdad0 100644 --- a/src/Data/Aeson/Types/FromJSON.hs +++ b/src/Data/Aeson/Types/FromJSON.hs @@ -1123,14 +1123,14 @@ parseNonAllNullarySum p@(tname :* opts :* _) = ", but found tag " ++ show tag cnames_ = unTagged2 (constructorTags (constructorTagModifier opts) :: Tagged2 f [String]) - TaggedFlatObject{..} -> + TaggedFlatObject{..} -> withObject tname $ \obj -> do let tagKey = pack tagFieldName badTag tag = failWith_ $ \cnames -> "expected tag field to be one of " ++ show cnames ++ ", but found tag " ++ show tag cnames_ = unTagged2 (constructorTags (constructorTagModifier opts) :: Tagged2 f [String]) - tag <- contextType tname . contextTag tagKey cnames_ $ obj .: tagKey + tag <- contextType tname . contextTag tagKey cnames_ $ obj .: tagKey fromMaybe (badTag tag Key tagKey) $ parseTaggedFlatObject (tag :* p) obj @@ -1454,9 +1454,9 @@ instance (RecordFromJSON arity f, FieldNames f) => FromTaggedFlatObject' arity f instance FromTaggedFlatObject' arity U1 False where parseTaggedFlatObject' _ _ = Tagged (pure U1) -instance OVERLAPPABLE_ PositionFromObject 1 arity f => FromTaggedFlatObject' arity f False where - parseTaggedFlatObject' (_ :* p) obj = Tagged (positionFromObject (Proxy :: Proxy 1) p obj) - +instance OVERLAPPABLE_ PositionFromObject 0 arity f => FromTaggedFlatObject' arity f False where + parseTaggedFlatObject' (_ :* p) obj = Tagged (positionFromObject (Proxy :: Proxy 0) p obj) + class KnownNat n => PositionFromObject n arity f where positionFromObject :: Proxy n -> TypeName :* Options :* FromArgs arity a diff --git a/src/Data/Aeson/Types/Internal.hs b/src/Data/Aeson/Types/Internal.hs index a5bf84801..e9363ebc3 100644 --- a/src/Data/Aeson/Types/Internal.hs +++ b/src/Data/Aeson/Types/Internal.hs @@ -711,7 +711,7 @@ data SumEncoding = -- in that data type with an additional field '$tagFieldName'. -- For example, given @A@ defined as -- @data A = A Int Int | B@, - -- this option will encode @A 1 2@ as @{"1": 1, "2": 2, "$tagFieldName": \"A"}@ + -- this option will encode @A 1 2@ as @{"0": 1, "1": 2, "$tagFieldName": \"A"}@ -- 5. The behavior is undefined when the '$tagFieldName' collides with another field name and should -- not be relied upon. It may or may not overwite the field. -- It may or may not throw an runtime exception. It may or may not raise an compile time error. diff --git a/src/Data/Aeson/Types/ToJSON.hs b/src/Data/Aeson/Types/ToJSON.hs index 762daf06d..bc19c363b 100644 --- a/src/Data/Aeson/Types/ToJSON.hs +++ b/src/Data/Aeson/Types/ToJSON.hs @@ -954,8 +954,8 @@ instance RecordToPairs pairs enc arity f => TaggedFlatObject' pairs enc arity f instance Monoid pairs => TaggedFlatObject' enc pairs arity U1 False where taggedFlatObject' _ _ _ = Tagged mempty -instance OVERLAPPABLE_ PositionToPairs 1 pairs enc arity f => TaggedFlatObject' enc pairs arity f False where - taggedFlatObject' opts targs a = Tagged $ positionToPairs (Proxy :: Proxy 1) opts targs a +instance OVERLAPPABLE_ PositionToPairs 0 pairs enc arity f => TaggedFlatObject' enc pairs arity f False where + taggedFlatObject' opts targs a = Tagged $ positionToPairs (Proxy :: Proxy 0) opts targs a class KnownNat n => PositionToPairs n pairs enc arity f where positionToPairs :: Proxy n -> Options -> ToArgs enc arity a -> f a -> pairs