jordan 0.1.0.0 → 0.2.0.0
raw patch · 27 files changed
+3279/−1434 lines, 27 filesdep +deepseqdep +ghc-primdep −megaparsecdep ~basedep ~textPVP ok
version bump matches the API change (PVP)
Dependencies added: deepseq, ghc-prim
Dependencies removed: megaparsec
Dependency ranges changed: base, text
API changes (from Hackage documentation)
- Jordan: megaparsecParser :: FromJSON val => Parser val
- Jordan: parseViaMegaparsec :: forall val. FromJSON val => Text -> Either String val
- Jordan: runParserViaAttoparsec :: (forall parser. JSONParser parser => parser a) -> ByteString -> Either String a
- Jordan: runParserViaMegaparsec :: (forall parser. JSONParser parser => parser a) -> Text -> Either String a
- Jordan: toJSONText :: ToJSON a => a -> Text
- Jordan: writeField :: JSONObjectSerializer f => Text -> (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) -> f a
- Jordan.FromJSON.Attoparsec: convertParserToAttoparsecParser :: (forall parser. JSONParser parser => parser a) -> Parser a
- Jordan.FromJSON.Attoparsec: runParserViaAttoparsec :: (forall parser. JSONParser parser => parser a) -> ByteString -> Either String a
- Jordan.FromJSON.Class: instance (GHC.Generics.Constructor t, GHC.Generics.Constructor t') => Jordan.FromJSON.Class.GFromJSON (GHC.Generics.C1 t GHC.Generics.U1 GHC.Generics.:+: GHC.Generics.C1 t' GHC.Generics.U1)
- Jordan.FromJSON.Class: instance (GHC.Real.Integral a, Jordan.FromJSON.Class.FromJSON a, Data.Typeable.Internal.Typeable a) => Jordan.FromJSON.Class.FromJSON (GHC.Real.Ratio a)
- Jordan.FromJSON.Class: instance (Jordan.FromJSON.Class.GFromJSONObject i, GHC.Generics.Constructor c) => Jordan.FromJSON.Class.GFromJSON (GHC.Generics.C1 c i)
- Jordan.FromJSON.Class: instance GHC.Generics.Constructor t => Jordan.FromJSON.Class.GFromJSON (GHC.Generics.C1 t GHC.Generics.U1)
- Jordan.FromJSON.Class: instance GHC.Generics.Constructor t => Jordan.FromJSON.Class.GFromJSON (Jordan.Generic.Options.PartOfSum (GHC.Generics.C1 t GHC.Generics.U1))
- Jordan.FromJSON.Class: instance Jordan.FromJSON.Class.FromJSON s => Jordan.FromJSON.Class.GFromJSON (GHC.Generics.C1 c (GHC.Generics.S1 ('GHC.Generics.MetaSel 'GHC.Maybe.Nothing su ss ds) (GHC.Generics.Rec0 s)))
- Jordan.FromJSON.Class: instance Jordan.FromJSON.Class.GFromJSON GHC.Generics.U1
- Jordan.FromJSON.Megaparsec: ArrayParser :: Parser a -> ArrayParser a
- Jordan.FromJSON.Megaparsec: ErrorContext :: [Text] -> ErrorContext
- Jordan.FromJSON.Megaparsec: MegaparsecParser :: Parser a -> MegaparsecParser a
- Jordan.FromJSON.Megaparsec: ObjectParser :: Permutation Parser a -> ObjectParser a
- Jordan.FromJSON.Megaparsec: [getArrayParser] :: ArrayParser a -> Parser a
- Jordan.FromJSON.Megaparsec: [getErrorContext] :: ErrorContext -> [Text]
- Jordan.FromJSON.Megaparsec: [getMegaparsecParser] :: MegaparsecParser a -> Parser a
- Jordan.FromJSON.Megaparsec: [getObjectParser] :: ObjectParser a -> Permutation Parser a
- Jordan.FromJSON.Megaparsec: comma :: Parser ()
- Jordan.FromJSON.Megaparsec: consumeJunkValue :: Parser ()
- Jordan.FromJSON.Megaparsec: convertParserToMegaparsecParser :: (forall parser. JSONParser parser => parser a) -> Parser a
- Jordan.FromJSON.Megaparsec: innerText :: Parser Text
- Jordan.FromJSON.Megaparsec: instance GHC.Base.Applicative Jordan.FromJSON.Megaparsec.ArrayParser
- Jordan.FromJSON.Megaparsec: instance GHC.Base.Applicative Jordan.FromJSON.Megaparsec.ObjectParser
- Jordan.FromJSON.Megaparsec: instance GHC.Base.Functor Jordan.FromJSON.Megaparsec.ArrayParser
- Jordan.FromJSON.Megaparsec: instance GHC.Base.Functor Jordan.FromJSON.Megaparsec.MegaparsecParser
- Jordan.FromJSON.Megaparsec: instance GHC.Base.Functor Jordan.FromJSON.Megaparsec.ObjectParser
- Jordan.FromJSON.Megaparsec: instance GHC.Base.Monoid (Jordan.FromJSON.Megaparsec.MegaparsecParser a)
- Jordan.FromJSON.Megaparsec: instance GHC.Base.Semigroup (Jordan.FromJSON.Megaparsec.MegaparsecParser a)
- Jordan.FromJSON.Megaparsec: instance GHC.Classes.Eq Jordan.FromJSON.Megaparsec.ErrorContext
- Jordan.FromJSON.Megaparsec: instance GHC.Classes.Ord Jordan.FromJSON.Megaparsec.ErrorContext
- Jordan.FromJSON.Megaparsec: instance GHC.Show.Show Jordan.FromJSON.Megaparsec.ErrorContext
- Jordan.FromJSON.Megaparsec: instance Jordan.FromJSON.Class.JSONObjectParser Jordan.FromJSON.Megaparsec.ObjectParser
- Jordan.FromJSON.Megaparsec: instance Jordan.FromJSON.Class.JSONParser Jordan.FromJSON.Megaparsec.MegaparsecParser
- Jordan.FromJSON.Megaparsec: instance Jordan.FromJSON.Class.JSONTupleParser Jordan.FromJSON.Megaparsec.ArrayParser
- Jordan.FromJSON.Megaparsec: instance Text.Megaparsec.Error.ShowErrorComponent Jordan.FromJSON.Megaparsec.ErrorContext
- Jordan.FromJSON.Megaparsec: junkFieldsAtEnd :: Parser ()
- Jordan.FromJSON.Megaparsec: labelSep :: Parser ()
- Jordan.FromJSON.Megaparsec: lexeme :: Parser a -> Parser a
- Jordan.FromJSON.Megaparsec: megaparsecParser :: FromJSON val => Parser val
- Jordan.FromJSON.Megaparsec: newtype ArrayParser a
- Jordan.FromJSON.Megaparsec: newtype ErrorContext
- Jordan.FromJSON.Megaparsec: newtype MegaparsecParser a
- Jordan.FromJSON.Megaparsec: newtype ObjectParser a
- Jordan.FromJSON.Megaparsec: objectKey :: Text -> Parser ()
- Jordan.FromJSON.Megaparsec: parseAnyArray :: Parser ()
- Jordan.FromJSON.Megaparsec: parseAnyField :: Parser ()
- Jordan.FromJSON.Megaparsec: parseAnyObject :: Parser ()
- Jordan.FromJSON.Megaparsec: parseCharInText :: Char -> Parser ()
- Jordan.FromJSON.Megaparsec: parseDictField :: Parser a -> Parser (Text, a)
- Jordan.FromJSON.Megaparsec: parseEscape :: Parser Text
- Jordan.FromJSON.Megaparsec: parseHexDigit :: Parser Int
- Jordan.FromJSON.Megaparsec: parseJSONBool :: Parser Bool
- Jordan.FromJSON.Megaparsec: parseJSONNull :: Parser ()
- Jordan.FromJSON.Megaparsec: parseJSONNumber :: Parser Scientific
- Jordan.FromJSON.Megaparsec: parseJSONText :: Parser Text
- Jordan.FromJSON.Megaparsec: parseObjectField :: Text -> Parser a -> Parser a
- Jordan.FromJSON.Megaparsec: parseViaMegaparsec :: forall val. FromJSON val => Text -> Either String val
- Jordan.FromJSON.Megaparsec: runParserViaMegaparsec :: (forall parser. JSONParser parser => parser a) -> Text -> Either String a
- Jordan.FromJSON.Megaparsec: takeSpace :: Parser ()
- Jordan.FromJSON.Megaparsec: type ParseError = ParseErrorBundle Text ErrorContext
- Jordan.FromJSON.Megaparsec: type Parser = Parsec ErrorContext Text
- Jordan.FromJSON.ParseInternal: Branch :: Permutation parser (arg -> a) -> parser arg -> Branch parser a
- Jordan.FromJSON.ParseInternal: Choice :: [Branch parser a] -> Permutation parser a
- Jordan.FromJSON.ParseInternal: Empty :: a -> Permutation parser a
- Jordan.FromJSON.ParseInternal: asParser :: Alternative f => Permutation f a -> f a
- Jordan.FromJSON.ParseInternal: asPermutation :: Alternative f => f a -> Permutation f a
- Jordan.FromJSON.ParseInternal: data Branch parser a
- Jordan.FromJSON.ParseInternal: data Permutation parser a
- Jordan.FromJSON.ParseInternal: instance GHC.Base.Alternative m => GHC.Base.Applicative (Jordan.FromJSON.ParseInternal.Branch m)
- Jordan.FromJSON.ParseInternal: instance GHC.Base.Alternative m => GHC.Base.Applicative (Jordan.FromJSON.ParseInternal.Permutation m)
- Jordan.FromJSON.ParseInternal: instance GHC.Base.Functor m => GHC.Base.Functor (Jordan.FromJSON.ParseInternal.Branch m)
- Jordan.FromJSON.ParseInternal: instance GHC.Base.Functor m => GHC.Base.Functor (Jordan.FromJSON.ParseInternal.Permutation m)
- Jordan.FromJSON.ParseInternal: wrapEffect :: forall m a b. Alternative m => m b -> m b -> Permutation m a -> m a
- Jordan.Generic.Options: PartOfSum :: f a -> PartOfSum f a
- Jordan.ToJSON.Class: instance (Jordan.ToJSON.Class.GToJSON f, GHC.Generics.Selector t) => Jordan.ToJSON.Class.GToJSONObject (GHC.Generics.S1 t f)
- Jordan.ToJSON.Class: instance (Jordan.ToJSON.Class.GToJSONObject inner, GHC.Generics.Constructor t) => Jordan.ToJSON.Class.GToJSON (GHC.Generics.C1 t inner)
- Jordan.ToJSON.Class: instance GHC.Generics.Constructor t => Jordan.ToJSON.Class.GToJSON (Jordan.Generic.Options.PartOfSum (GHC.Generics.C1 t GHC.Generics.U1))
- Jordan.ToJSON.Class: instance Jordan.ToJSON.Class.GToJSON s => Jordan.ToJSON.Class.GToJSON (GHC.Generics.S1 whatever s)
- Jordan.ToJSON.Class: instance Jordan.ToJSON.Class.ToJSON i => Jordan.ToJSON.Class.GToJSON (GHC.Generics.C1 c (GHC.Generics.S1 ('GHC.Generics.MetaSel 'GHC.Maybe.Nothing su ss ds) (GHC.Generics.Rec0 i)))
- Jordan.ToJSON.Class: writeField :: JSONObjectSerializer f => Text -> (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) -> f a
- Jordan.ToJSON.Class: writeItem :: JSONTupleSerializer f => (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) -> f a
- Jordan.ToJSON.Text: CommaBuilder :: (v -> TextComma) -> CommaBuilder v
- Jordan.ToJSON.Text: Empty :: TextComma
- Jordan.ToJSON.Text: JSONText :: (a -> Text -> Text) -> JSONText a
- Jordan.ToJSON.Text: TextArray :: (v -> [Text] -> [Text]) -> TextArray v
- Jordan.ToJSON.Text: Written :: (Text -> Text) -> TextComma
- Jordan.ToJSON.Text: [runCommaBuilder] :: CommaBuilder v -> v -> TextComma
- Jordan.ToJSON.Text: [runJSONText] :: JSONText a -> a -> Text -> Text
- Jordan.ToJSON.Text: [runTextArray] :: TextArray v -> v -> [Text] -> [Text]
- Jordan.ToJSON.Text: convChar :: Char -> Text -> Text
- Jordan.ToJSON.Text: data TextComma
- Jordan.ToJSON.Text: instance Data.Functor.Contravariant.Contravariant Jordan.ToJSON.Text.CommaBuilder
- Jordan.ToJSON.Text: instance Data.Functor.Contravariant.Contravariant Jordan.ToJSON.Text.JSONText
- Jordan.ToJSON.Text: instance Data.Functor.Contravariant.Contravariant Jordan.ToJSON.Text.TextArray
- Jordan.ToJSON.Text: instance Data.Functor.Contravariant.Divisible.Decidable Jordan.ToJSON.Text.TextArray
- Jordan.ToJSON.Text: instance Data.Functor.Contravariant.Divisible.Divisible Jordan.ToJSON.Text.CommaBuilder
- Jordan.ToJSON.Text: instance Data.Functor.Contravariant.Divisible.Divisible Jordan.ToJSON.Text.TextArray
- Jordan.ToJSON.Text: instance Data.String.IsString Jordan.ToJSON.Text.TextComma
- Jordan.ToJSON.Text: instance GHC.Base.Monoid (Jordan.ToJSON.Text.CommaBuilder v)
- Jordan.ToJSON.Text: instance GHC.Base.Monoid (Jordan.ToJSON.Text.JSONText a)
- Jordan.ToJSON.Text: instance GHC.Base.Monoid (Jordan.ToJSON.Text.TextArray v)
- Jordan.ToJSON.Text: instance GHC.Base.Monoid Jordan.ToJSON.Text.TextComma
- Jordan.ToJSON.Text: instance GHC.Base.Semigroup (Jordan.ToJSON.Text.CommaBuilder v)
- Jordan.ToJSON.Text: instance GHC.Base.Semigroup (Jordan.ToJSON.Text.JSONText a)
- Jordan.ToJSON.Text: instance GHC.Base.Semigroup (Jordan.ToJSON.Text.TextArray v)
- Jordan.ToJSON.Text: instance GHC.Base.Semigroup Jordan.ToJSON.Text.TextComma
- Jordan.ToJSON.Text: instance Jordan.ToJSON.Class.JSONObjectSerializer Jordan.ToJSON.Text.CommaBuilder
- Jordan.ToJSON.Text: instance Jordan.ToJSON.Class.JSONSerializer Jordan.ToJSON.Text.JSONText
- Jordan.ToJSON.Text: instance Jordan.ToJSON.Class.JSONTupleSerializer Jordan.ToJSON.Text.CommaBuilder
- Jordan.ToJSON.Text: instance Jordan.ToJSON.Class.JSONTupleSerializer Jordan.ToJSON.Text.TextArray
- Jordan.ToJSON.Text: instance Jordan.ToJSON.Class.Selectable Jordan.ToJSON.Text.JSONText
- Jordan.ToJSON.Text: isBadChar :: Char -> Bool
- Jordan.ToJSON.Text: newtype CommaBuilder v
- Jordan.ToJSON.Text: newtype JSONText a
- Jordan.ToJSON.Text: newtype TextArray v
- Jordan.ToJSON.Text: quoteString :: Text -> Text -> Text
- Jordan.ToJSON.Text: runCommaBuilder' :: CommaBuilder v -> v -> Text -> Text
- Jordan.ToJSON.Text: runWritten :: TextComma -> Text -> Text
- Jordan.ToJSON.Text: sArray :: (a -> Text -> Text) -> [a] -> Text -> Text
- Jordan.ToJSON.Text: toJSONText :: ToJSON a => a -> Text
+ Jordan: (>$) :: Contravariant f => b -> f b -> f a
+ Jordan: JArray :: [JSONValue] -> JSONValue
+ Jordan: JBool :: Bool -> JSONValue
+ Jordan: JNull :: JSONValue
+ Jordan: JNumber :: Scientific -> JSONValue
+ Jordan: JObject :: Map Text JSONValue -> JSONValue
+ Jordan: JText :: Text -> JSONValue
+ Jordan: WithOptions :: a -> WithOptions (options :: [*]) a
+ Jordan: [fromJSONOmitNothingFields] :: FromJSONOptions -> Bool
+ Jordan: [getWithOptions] :: WithOptions (options :: [*]) a -> a
+ Jordan: [toJSONOmitNothingFields] :: ToJSONOptions -> Bool
+ Jordan: addFormat :: JSONParser f => Text -> f a -> f a
+ Jordan: attoparsecParserFor :: (forall parser. JSONParser parser => parser a) -> Parser a
+ Jordan: class Contravariant (f :: Type -> Type)
+ Jordan: class Contravariant f => Divisible (f :: Type -> Type)
+ Jordan: class (Contravariant f) => Selectable f
+ Jordan: conquer :: Divisible f => f a
+ Jordan: contramap :: Contravariant f => (a -> b) -> f b -> f a
+ Jordan: data JSONValue
+ Jordan: data KeepNothingFields
+ Jordan: data OmitNothingFields
+ Jordan: divide :: Divisible f => (a -> (b, c)) -> f b -> f c -> f a
+ Jordan: giveUp :: Selectable f => (arg -> Void) -> f arg
+ Jordan: infixl 4 >$
+ Jordan: nameParser :: JSONParser f => Text -> f a -> f a
+ Jordan: nameSerializer :: JSONSerializer f => Text -> f a -> f a
+ Jordan: newtype WithOptions (options :: [*]) a
+ Jordan: parseDescribeField :: (JSONObjectParser f, FromJSON v) => Text -> Text -> f v
+ Jordan: parseDescribeFieldWith :: JSONObjectParser f => Text -> Text -> (forall valueParser. JSONParser valueParser => valueParser a) -> f a
+ Jordan: parseDescribeFieldWithDefault :: JSONObjectParser f => Text -> Text -> (forall valueParser. JSONParser valueParser => valueParser a) -> a -> f a
+ Jordan: parseFieldWithDefault :: JSONObjectParser f => Text -> (forall valueParser. JSONParser valueParser => valueParser a) -> a -> f a
+ Jordan: parseInteger :: JSONParser f => f Integer
+ Jordan: parseOrReport :: FromJSON a => ByteString -> Either JSONError a
+ Jordan: parseOrReportWith :: (forall parser. JSONParser parser => parser a) -> ByteString -> Either JSONError a
+ Jordan: parseViaAttoparsecWith :: (forall parser. JSONParser parser => parser a) -> ByteString -> Either String a
+ Jordan: select :: Selectable f => (arg -> Either lhs rhs) -> f lhs -> f rhs -> f arg
+ Jordan: serializeDescribeFieldWith :: JSONObjectSerializer f => Text -> Text -> (forall valueSerializer. JSONSerializer valueSerializer => valueSerializer a) -> f a
+ Jordan: serializeField :: (JSONObjectSerializer f, ToJSON a) => Text -> f a
+ Jordan: serializeFieldWith :: JSONObjectSerializer f => Text -> (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) -> f a
+ Jordan: serializeJust :: JSONObjectSerializer f => Text -> (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) -> f (Maybe a)
+ Jordan.FromJSON.Attoparsec: attoparsecParserFor :: (forall parser. JSONParser parser => parser a) -> Parser a
+ Jordan.FromJSON.Attoparsec: parseViaAttoparsecWith :: (forall parser. JSONParser parser => parser a) -> ByteString -> Either String a
+ Jordan.FromJSON.Class: [fromJSONOmitNothingFields] :: FromJSONOptions -> Bool
+ Jordan.FromJSON.Class: addFormat :: JSONParser f => Text -> f a -> f a
+ Jordan.FromJSON.Class: class GFromJSONTuple v
+ Jordan.FromJSON.Class: class SpecifiesFromJSONOptions (a :: [*])
+ Jordan.FromJSON.Class: gFromJSONTuple :: (GFromJSONTuple v, JSONTupleParser f) => FromJSONOptions -> f (v a)
+ Jordan.FromJSON.Class: instance (GHC.Generics.Generic a, Jordan.FromJSON.Class.GFromJSON (GHC.Generics.Rep a), Data.Typeable.Internal.Typeable a, Jordan.FromJSON.Class.SpecifiesFromJSONOptions options) => Jordan.FromJSON.Class.FromJSON (Jordan.Generic.Options.WithOptions options a)
+ Jordan.FromJSON.Class: instance (GHC.Real.Integral a, Jordan.FromJSON.Class.FromJSON a) => Jordan.FromJSON.Class.FromJSON (GHC.Real.Ratio a)
+ Jordan.FromJSON.Class: instance (Jordan.FromJSON.Class.FromJSON c, GHC.Generics.Selector t) => Jordan.FromJSON.Class.GFromJSONObject (GHC.Generics.S1 t (GHC.Generics.K1 v (GHC.Maybe.Maybe c)))
+ Jordan.FromJSON.Class: instance (Jordan.FromJSON.Class.FromJSON inner, GHC.TypeLits.KnownSymbol n) => Jordan.FromJSON.Class.GFromJSON (GHC.Generics.C1 ('GHC.Generics.MetaCons n s 'GHC.Types.False) (GHC.Generics.S1 ('GHC.Generics.MetaSel 'GHC.Maybe.Nothing ss su dl) (GHC.Generics.Rec0 inner)))
+ Jordan.FromJSON.Class: instance (Jordan.FromJSON.Class.GFromJSONObject i, GHC.TypeLits.KnownSymbol n) => Jordan.FromJSON.Class.GFromJSON (GHC.Generics.C1 ('GHC.Generics.MetaCons n s 'GHC.Types.True) i)
+ Jordan.FromJSON.Class: instance (Jordan.FromJSON.Class.GFromJSONTuple inner, GHC.TypeLits.KnownSymbol n) => Jordan.FromJSON.Class.GFromJSON (GHC.Generics.C1 ('GHC.Generics.MetaCons n s 'GHC.Types.False) inner)
+ Jordan.FromJSON.Class: instance (Jordan.FromJSON.Class.GFromJSONTuple lhs, Jordan.FromJSON.Class.GFromJSONTuple rhs) => Jordan.FromJSON.Class.GFromJSONTuple (lhs GHC.Generics.:*: rhs)
+ Jordan.FromJSON.Class: instance GHC.TypeLits.KnownSymbol connName => Jordan.FromJSON.Class.GFromJSON (GHC.Generics.C1 ('GHC.Generics.MetaCons connName dontCare 'GHC.Types.False) GHC.Generics.U1)
+ Jordan.FromJSON.Class: instance GHC.TypeLits.KnownSymbol connName => Jordan.FromJSON.Class.GFromJSON (Jordan.Generic.Options.PartOfSum (GHC.Generics.C1 ('GHC.Generics.MetaCons connName dontCare 'GHC.Types.False) GHC.Generics.U1))
+ Jordan.FromJSON.Class: instance Jordan.FromJSON.Class.FromJSON GHC.Int.Int32
+ Jordan.FromJSON.Class: instance Jordan.FromJSON.Class.FromJSON GHC.Int.Int64
+ Jordan.FromJSON.Class: instance Jordan.FromJSON.Class.FromJSON a => Jordan.FromJSON.Class.FromJSON (Data.Map.Internal.Map GHC.Integer.Type.Integer a)
+ Jordan.FromJSON.Class: instance Jordan.FromJSON.Class.FromJSON a => Jordan.FromJSON.Class.FromJSON (GHC.Base.NonEmpty a)
+ Jordan.FromJSON.Class: instance Jordan.FromJSON.Class.GFromJSON f => Jordan.FromJSON.Class.GFromJSONTuple (GHC.Generics.S1 ('GHC.Generics.MetaSel 'GHC.Maybe.Nothing su ss ds) f)
+ Jordan.FromJSON.Class: instance Jordan.FromJSON.Class.SpecifiesFromJSONOptions '[]
+ Jordan.FromJSON.Class: instance Jordan.FromJSON.Class.SpecifiesFromJSONOptions xs => Jordan.FromJSON.Class.SpecifiesFromJSONOptions (Jordan.Generic.Options.KeepNothingFields : xs)
+ Jordan.FromJSON.Class: instance Jordan.FromJSON.Class.SpecifiesFromJSONOptions xs => Jordan.FromJSON.Class.SpecifiesFromJSONOptions (Jordan.Generic.Options.OmitNothingFields : xs)
+ Jordan.FromJSON.Class: nameParser :: JSONParser f => Text -> f a -> f a
+ Jordan.FromJSON.Class: parseDescribeField :: (JSONObjectParser f, FromJSON v) => Text -> Text -> f v
+ Jordan.FromJSON.Class: parseDescribeFieldWith :: JSONObjectParser f => Text -> Text -> (forall valueParser. JSONParser valueParser => valueParser a) -> f a
+ Jordan.FromJSON.Class: parseDescribeFieldWithDefault :: JSONObjectParser f => Text -> Text -> (forall valueParser. JSONParser valueParser => valueParser a) -> a -> f a
+ Jordan.FromJSON.Class: parseFieldWithDefault :: JSONObjectParser f => Text -> (forall valueParser. JSONParser valueParser => valueParser a) -> a -> f a
+ Jordan.FromJSON.Class: parseInteger :: JSONParser f => f Integer
+ Jordan.FromJSON.Class: specifiedFromJSONOptions :: SpecifiesFromJSONOptions a => FromJSONOptions
+ Jordan.FromJSON.Internal.Attoparsec: SP :: !Integer -> {-# UNPACK #-} !Int -> SP
+ Jordan.FromJSON.Internal.Attoparsec: anyArray :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: anyDatum :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: anyObject :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: bsToInteger :: ByteString -> Integer
+ Jordan.FromJSON.Internal.Attoparsec: bsToIntegerSimple :: ByteString -> Integer
+ Jordan.FromJSON.Internal.Attoparsec: canParseDirectly :: Text -> Bool
+ Jordan.FromJSON.Internal.Attoparsec: comma :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: data SP
+ Jordan.FromJSON.Internal.Attoparsec: decimal0 :: Parser Integer
+ Jordan.FromJSON.Internal.Attoparsec: endArray :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: endObject :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: inObjectBraces :: Parser a -> Parser a
+ Jordan.FromJSON.Internal.Attoparsec: innerText :: Parser Text
+ Jordan.FromJSON.Internal.Attoparsec: junkInnerText :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: label :: String -> Parser a -> Parser a
+ Jordan.FromJSON.Internal.Attoparsec: labelSep :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: lexeme :: Parser a -> Parser a
+ Jordan.FromJSON.Internal.Attoparsec: mustBeEscaped :: Char -> Bool
+ Jordan.FromJSON.Internal.Attoparsec: number :: Parser Scientific
+ Jordan.FromJSON.Internal.Attoparsec: objectEndWithJunk :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: objectKey :: Text -> Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: parseAnyField :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: parseCharInText :: Char -> Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: parseDictField :: Parser a -> Parser (Text, a)
+ Jordan.FromJSON.Internal.Attoparsec: parseEscape :: Parser Text
+ Jordan.FromJSON.Internal.Attoparsec: parseHexDigit :: Parser Int
+ Jordan.FromJSON.Internal.Attoparsec: parseJSONText :: Parser Text
+ Jordan.FromJSON.Internal.Attoparsec: parseJunkDecimalZero :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: parseJunkExponent :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: parseJunkNumber :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: parseJunkText :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: parseObjectField :: Text -> Parser a -> Parser a
+ Jordan.FromJSON.Internal.Attoparsec: parseTextBody :: Text -> Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: parseViaChars :: Text -> Parser ByteString ()
+ Jordan.FromJSON.Internal.Attoparsec: quotation :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: scientific :: Parser Scientific
+ Jordan.FromJSON.Internal.Attoparsec: skipSpace :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: startArray :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: startObject :: Parser ()
+ Jordan.FromJSON.Internal.Attoparsec: valInteger :: Integer -> Int -> [Integer] -> Integer
+ Jordan.FromJSON.Internal.Attoparsec: valSimple :: Integer -> [Integer] -> Integer
+ Jordan.FromJSON.Internal.Permutation: Branch :: Permutation parser (arg -> a) -> parser arg -> Branch parser a
+ Jordan.FromJSON.Internal.Permutation: FailingParser :: (forall a. parser a) -> FailingParser parser
+ Jordan.FromJSON.Internal.Permutation: NoFailingParser :: FailingParser parser
+ Jordan.FromJSON.Internal.Permutation: Permutation :: !Maybe a -> !FailingParser parser -> [Branch parser a] -> Permutation parser a
+ Jordan.FromJSON.Internal.Permutation: asParser :: Alternative f => Permutation f a -> f a
+ Jordan.FromJSON.Internal.Permutation: asPermutation :: Alternative f => f a -> Permutation f a
+ Jordan.FromJSON.Internal.Permutation: asPermutationWithDefault :: Alternative f => f a -> a -> Permutation f a
+ Jordan.FromJSON.Internal.Permutation: asPermutationWithDefaultFailing :: Alternative f => f a -> (forall b. f b) -> a -> Permutation f a
+ Jordan.FromJSON.Internal.Permutation: asPermutationWithFailing :: Alternative f => f a -> (forall b. f b) -> Permutation f a
+ Jordan.FromJSON.Internal.Permutation: data Branch parser a
+ Jordan.FromJSON.Internal.Permutation: data FailingParser parser
+ Jordan.FromJSON.Internal.Permutation: data Permutation parser a
+ Jordan.FromJSON.Internal.Permutation: eliminateFailing :: Alternative parser => FailingParser parser -> parser a
+ Jordan.FromJSON.Internal.Permutation: instance GHC.Base.Alternative m => GHC.Base.Applicative (Jordan.FromJSON.Internal.Permutation.Branch m)
+ Jordan.FromJSON.Internal.Permutation: instance GHC.Base.Alternative m => GHC.Base.Applicative (Jordan.FromJSON.Internal.Permutation.Permutation m)
+ Jordan.FromJSON.Internal.Permutation: instance GHC.Base.Applicative parser => GHC.Base.Semigroup (Jordan.FromJSON.Internal.Permutation.FailingParser parser)
+ Jordan.FromJSON.Internal.Permutation: instance GHC.Base.Functor m => GHC.Base.Functor (Jordan.FromJSON.Internal.Permutation.Branch m)
+ Jordan.FromJSON.Internal.Permutation: instance GHC.Base.Functor m => GHC.Base.Functor (Jordan.FromJSON.Internal.Permutation.Permutation m)
+ Jordan.FromJSON.Internal.Permutation: wrapEffect :: forall m a b. Alternative m => m b -> m b -> Permutation m a -> m a
+ Jordan.FromJSON.Internal.UnboxedParser: AccumE :: (# err | a #) -> AccumE err a
+ Jordan.FromJSON.Internal.UnboxedParser: InputRead# :: (# ForeignPtrContents, Addr#, Int# #) -> InputRead
+ Jordan.FromJSON.Internal.UnboxedParser: InputState# :: Int# -> InputState
+ Jordan.FromJSON.Internal.UnboxedParser: ParseResult# :: (# (# InputState, AccumE err res #) | (# #) #) -> ParseResult# err res
+ Jordan.FromJSON.Internal.UnboxedParser: Parser :: Parser# RealWorld err res -> Parser err res
+ Jordan.FromJSON.Internal.UnboxedParser: Parser# :: (InputRead -> InputState -> State# s -> (# State# s, ParseResult# err res #)) -> Parser# s err res
+ Jordan.FromJSON.Internal.UnboxedParser: [getAccumE] :: AccumE err a -> (# err | a #)
+ Jordan.FromJSON.Internal.UnboxedParser: [getInputOffset] :: InputState -> Int#
+ Jordan.FromJSON.Internal.UnboxedParser: [getInputRead] :: InputRead -> (# ForeignPtrContents, Addr#, Int# #)
+ Jordan.FromJSON.Internal.UnboxedParser: [getParseResult#] :: ParseResult# err res -> (# (# InputState, AccumE err res #) | (# #) #)
+ Jordan.FromJSON.Internal.UnboxedParser: [getParser] :: Parser err res -> Parser# RealWorld err res
+ Jordan.FromJSON.Internal.UnboxedParser: [runParser] :: Parser# s err res -> InputRead -> InputState -> State# s -> (# State# s, ParseResult# err res #)
+ Jordan.FromJSON.Internal.UnboxedParser: accSet :: a1 -> AccumE err a2 -> AccumE err a1
+ Jordan.FromJSON.Internal.UnboxedParser: advanceWord :: Parser err ()
+ Jordan.FromJSON.Internal.UnboxedParser: altParser :: Monoid err => Parser# s err a -> Parser# s err a -> Parser# s err a
+ Jordan.FromJSON.Internal.UnboxedParser: apParser :: Semigroup err => Parser# s err (a -> b) -> Parser# s err a -> Parser# s err b
+ Jordan.FromJSON.Internal.UnboxedParser: appAcc :: Semigroup err => AccumE err (a1 -> a2) -> AccumE err a1 -> AccumE err a2
+ Jordan.FromJSON.Internal.UnboxedParser: asFailure :: Parser err err -> Parser err a
+ Jordan.FromJSON.Internal.UnboxedParser: bimapAcc :: (err -> err') -> (a -> a') -> AccumE err a -> AccumE err' a'
+ Jordan.FromJSON.Internal.UnboxedParser: bimapParser :: (err -> err') -> (a -> a') -> Parser# s err a -> Parser# s err' a'
+ Jordan.FromJSON.Internal.UnboxedParser: bindParser :: Parser# s err a -> (a -> Parser# s err b) -> Parser# s err b
+ Jordan.FromJSON.Internal.UnboxedParser: chunkOfLength :: Int -> Parser err ByteString
+ Jordan.FromJSON.Internal.UnboxedParser: currentEnv :: Parser err (ForeignPtrContents, Ptr a, Int)
+ Jordan.FromJSON.Internal.UnboxedParser: currentOffset :: Parser err Int
+ Jordan.FromJSON.Internal.UnboxedParser: eitherAcc :: Either err a -> AccumE err a
+ Jordan.FromJSON.Internal.UnboxedParser: emptyParser :: Parser# s err a
+ Jordan.FromJSON.Internal.UnboxedParser: failParse :: Parser err a
+ Jordan.FromJSON.Internal.UnboxedParser: failWith :: err -> Parser err a
+ Jordan.FromJSON.Internal.UnboxedParser: fmapParser :: (a -> res) -> Parser# s err a -> Parser# s err res
+ Jordan.FromJSON.Internal.UnboxedParser: getEndOffset :: Parser err Int
+ Jordan.FromJSON.Internal.UnboxedParser: hasFurther :: Parser err Bool
+ Jordan.FromJSON.Internal.UnboxedParser: instance Data.Bifunctor.Bifunctor Jordan.FromJSON.Internal.UnboxedParser.Parser
+ Jordan.FromJSON.Internal.UnboxedParser: instance GHC.Base.Functor (Jordan.FromJSON.Internal.UnboxedParser.Parser err)
+ Jordan.FromJSON.Internal.UnboxedParser: instance GHC.Base.Monoid err => GHC.Base.Alternative (Jordan.FromJSON.Internal.UnboxedParser.Parser err)
+ Jordan.FromJSON.Internal.UnboxedParser: instance GHC.Base.Monoid err => GHC.Base.Monoid (Jordan.FromJSON.Internal.UnboxedParser.Parser err res)
+ Jordan.FromJSON.Internal.UnboxedParser: instance GHC.Base.Monoid err => GHC.Base.Semigroup (Jordan.FromJSON.Internal.UnboxedParser.Parser err res)
+ Jordan.FromJSON.Internal.UnboxedParser: instance GHC.Base.Semigroup err => GHC.Base.Applicative (Jordan.FromJSON.Internal.UnboxedParser.Parser err)
+ Jordan.FromJSON.Internal.UnboxedParser: instance GHC.Base.Semigroup err => GHC.Base.Monad (Jordan.FromJSON.Internal.UnboxedParser.Parser err)
+ Jordan.FromJSON.Internal.UnboxedParser: lowerErr :: Parser err (Either err a) -> Parser err a
+ Jordan.FromJSON.Internal.UnboxedParser: maxOffset :: InputState -> InputState -> InputState
+ Jordan.FromJSON.Internal.UnboxedParser: maybeWord :: Parser err (Maybe Word8)
+ Jordan.FromJSON.Internal.UnboxedParser: newtype AccumE err a
+ Jordan.FromJSON.Internal.UnboxedParser: newtype InputRead
+ Jordan.FromJSON.Internal.UnboxedParser: newtype InputState
+ Jordan.FromJSON.Internal.UnboxedParser: newtype ParseResult# err res
+ Jordan.FromJSON.Internal.UnboxedParser: newtype Parser err res
+ Jordan.FromJSON.Internal.UnboxedParser: newtype Parser# s err res
+ Jordan.FromJSON.Internal.UnboxedParser: orFail :: Parser err (Maybe a) -> Parser err a
+ Jordan.FromJSON.Internal.UnboxedParser: orNegative :: (Monoid err, Num a) => Parser err a -> Parser err a
+ Jordan.FromJSON.Internal.UnboxedParser: parseBS :: Parser err res -> ByteString -> Maybe (AccumE err res)
+ Jordan.FromJSON.Internal.UnboxedParser: parseBSIO :: Parser err res -> ByteString -> IO (Maybe (AccumE err res))
+ Jordan.FromJSON.Internal.UnboxedParser: parseChunk :: ByteString -> Parser err ()
+ Jordan.FromJSON.Internal.UnboxedParser: parseIntegral :: forall err i. (Monoid err, Integral i) => Parser err (Int, i)
+ Jordan.FromJSON.Internal.UnboxedParser: parseIntegralGo :: (Monoid err, Integral i) => Int -> i -> Parser err (Int, i)
+ Jordan.FromJSON.Internal.UnboxedParser: parseIntegralNoLeadingZero :: forall err i. (Monoid err, Integral i) => Parser err (Int, i)
+ Jordan.FromJSON.Internal.UnboxedParser: parsedPtr :: Parser err (ForeignPtr Word8)
+ Jordan.FromJSON.Internal.UnboxedParser: pattern AccumEL :: err -> AccumE err a
+ Jordan.FromJSON.Internal.UnboxedParser: pattern AccumER :: a -> AccumE err a
+ Jordan.FromJSON.Internal.UnboxedParser: pattern InputRead :: ForeignPtrContents -> Addr# -> Int# -> InputRead
+ Jordan.FromJSON.Internal.UnboxedParser: pattern InputState :: Int# -> InputState
+ Jordan.FromJSON.Internal.UnboxedParser: pattern JustParseResult :: InputState -> AccumE err res -> ParseResult# err res
+ Jordan.FromJSON.Internal.UnboxedParser: pattern NoParseResult :: ParseResult# err res
+ Jordan.FromJSON.Internal.UnboxedParser: peekRest :: Semigroup err => Parser err ByteString
+ Jordan.FromJSON.Internal.UnboxedParser: peekWord :: Parser err Word8
+ Jordan.FromJSON.Internal.UnboxedParser: peekWordMaybe :: Parser err (Maybe Word8)
+ Jordan.FromJSON.Internal.UnboxedParser: pureParser :: Semigroup err => a -> Parser# s err a
+ Jordan.FromJSON.Internal.UnboxedParser: signed :: (Monoid err, Num a) => Parser err a -> Parser err a
+ Jordan.FromJSON.Internal.UnboxedParser: skipWhitespace :: Parser err ()
+ Jordan.FromJSON.Internal.UnboxedParser: skipWhitespaceCB :: InputRead -> InputState -> State# RealWorld -> (# State# RealWorld, ParseResult# err () #)
+ Jordan.FromJSON.Internal.UnboxedParser: skipWord8 :: (Word8 -> Bool) -> Parser err ()
+ Jordan.FromJSON.Internal.UnboxedParser: skipWord8# :: (WordPrim -> Bool) -> Parser err ()
+ Jordan.FromJSON.Internal.UnboxedParser: skipWord8CB# :: (WordPrim -> Bool) -> InputRead -> InputState -> State# RealWorld -> (# State# RealWorld, ParseResult# err () #)
+ Jordan.FromJSON.Internal.UnboxedParser: specificWord :: Word8 -> Parser err ()
+ Jordan.FromJSON.Internal.UnboxedParser: takeWord8 :: Semigroup err => (Word8 -> Bool) -> Parser err ByteString
+ Jordan.FromJSON.Internal.UnboxedParser: takeWord81 :: Semigroup err => (Word8 -> Bool) -> Parser err ByteString
+ Jordan.FromJSON.Internal.UnboxedParser: takeWord81Cont :: Semigroup err => (Word8 -> Bool) -> (ByteString -> b) -> Parser err b
+ Jordan.FromJSON.Internal.UnboxedParser: takeWord8Cont :: Semigroup err => (Word8 -> Bool) -> (ByteString -> a) -> Parser err a
+ Jordan.FromJSON.Internal.UnboxedParser: testParser :: Parser () (Word8, ByteString)
+ Jordan.FromJSON.Internal.UnboxedParser: type WordPrim = Word#
+ Jordan.FromJSON.Internal.UnboxedParser: word :: Parser err Word8
+ Jordan.FromJSON.Internal.UnboxedReporting: SP :: !Integer -> {-# UNPACK #-} !Int -> SP
+ Jordan.FromJSON.Internal.UnboxedReporting: boolParser :: Monoid err => Parser err Bool
+ Jordan.FromJSON.Internal.UnboxedReporting: comma :: Semigroup err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: data SP
+ Jordan.FromJSON.Internal.UnboxedReporting: decimal0 :: Semigroup err => Parser err Integer
+ Jordan.FromJSON.Internal.UnboxedReporting: endArray :: Semigroup err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: endBracket :: Semigroup err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: failOnError :: Monoid err => Either a Text -> Parser err Text
+ Jordan.FromJSON.Internal.UnboxedReporting: hexDigit :: Semigroup err => Parser err Word8
+ Jordan.FromJSON.Internal.UnboxedReporting: invalidTextChar :: Char -> Bool
+ Jordan.FromJSON.Internal.UnboxedReporting: isDigitWord8 :: Word8 -> Bool
+ Jordan.FromJSON.Internal.UnboxedReporting: jsonTypeFromWord :: Word8 -> Maybe JSONType
+ Jordan.FromJSON.Internal.UnboxedReporting: kvSep :: Semigroup err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: lexeme :: Semigroup err => Parser err a -> Parser err a
+ Jordan.FromJSON.Internal.UnboxedReporting: nullParser :: Semigroup err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: parseAfterQuote :: Monoid err => Parser err Text
+ Jordan.FromJSON.Internal.UnboxedReporting: parseCharInText :: Monoid err => Char -> Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: parseEscape :: Monoid err => Parser err Text
+ Jordan.FromJSON.Internal.UnboxedReporting: parseObjectKV :: Monoid err => Text -> Parser err b -> Parser err b
+ Jordan.FromJSON.Internal.UnboxedReporting: parseSpecificKeyAfterQuote :: Monoid err => Text -> Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: parseSpecificKeyInQuotes :: Monoid err => Text -> Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: peekJSONType :: Monoid err => Parser err JSONType
+ Jordan.FromJSON.Internal.UnboxedReporting: scientific :: Monoid err => Parser err Scientific
+ Jordan.FromJSON.Internal.UnboxedReporting: sepByVoid :: Alternative f => f a1 -> f a2 -> f ()
+ Jordan.FromJSON.Internal.UnboxedReporting: skipAnyKV :: Monoid err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: skipAnything :: Monoid err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: skipArray :: Monoid err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: skipBoolExpecting :: JSONType -> Parser JSONError a
+ Jordan.FromJSON.Internal.UnboxedReporting: skipNullExpecting :: JSONType -> Parser JSONError a
+ Jordan.FromJSON.Internal.UnboxedReporting: skipNumber :: Monoid err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: skipNumberExpecting :: JSONType -> Parser JSONError a
+ Jordan.FromJSON.Internal.UnboxedReporting: skipObject :: Monoid err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: skipTextExpecting :: JSONType -> Parser JSONError a
+ Jordan.FromJSON.Internal.UnboxedReporting: skipWithFailure :: JSONError -> Parser JSONError a
+ Jordan.FromJSON.Internal.UnboxedReporting: startArray :: Semigroup err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: startBracket :: Semigroup err => Parser err ()
+ Jordan.FromJSON.Internal.UnboxedReporting: textParser :: Monoid err => Parser err Text
+ Jordan.FromJSON.Internal.UnboxedReporting: valInteger :: Integer -> Int -> [Integer] -> Integer
+ Jordan.FromJSON.Internal.UnboxedReporting: valSimple :: Integer -> [Integer] -> Integer
+ Jordan.FromJSON.UnboxedReporting: instance GHC.Base.Applicative Jordan.FromJSON.UnboxedReporting.ReportingObjectParser
+ Jordan.FromJSON.UnboxedReporting: instance GHC.Base.Applicative Jordan.FromJSON.UnboxedReporting.ReportingTupleParser
+ Jordan.FromJSON.UnboxedReporting: instance GHC.Base.Functor Jordan.FromJSON.UnboxedReporting.ReportingObjectParser
+ Jordan.FromJSON.UnboxedReporting: instance GHC.Base.Functor Jordan.FromJSON.UnboxedReporting.ReportingParser
+ Jordan.FromJSON.UnboxedReporting: instance GHC.Base.Functor Jordan.FromJSON.UnboxedReporting.ReportingTupleParser
+ Jordan.FromJSON.UnboxedReporting: instance GHC.Base.Semigroup (Jordan.FromJSON.UnboxedReporting.ReportingParser a)
+ Jordan.FromJSON.UnboxedReporting: instance Jordan.FromJSON.Class.JSONObjectParser Jordan.FromJSON.UnboxedReporting.ReportingObjectParser
+ Jordan.FromJSON.UnboxedReporting: instance Jordan.FromJSON.Class.JSONParser Jordan.FromJSON.UnboxedReporting.ReportingParser
+ Jordan.FromJSON.UnboxedReporting: instance Jordan.FromJSON.Class.JSONTupleParser Jordan.FromJSON.UnboxedReporting.ReportingTupleParser
+ Jordan.FromJSON.UnboxedReporting: parseOrReport :: FromJSON a => ByteString -> Either JSONError a
+ Jordan.FromJSON.UnboxedReporting: parseOrReportWith :: (forall parser. JSONParser parser => parser a) -> ByteString -> Either JSONError a
+ Jordan.Generic.Options: KeepNothingFields :: KeepNothingFields
+ Jordan.Generic.Options: MkPartOfSum :: f a -> PartOfSum f a
+ Jordan.Generic.Options: OmitNothingFields :: OmitNothingFields
+ Jordan.Generic.Options: WithOptions :: a -> WithOptions (options :: [*]) a
+ Jordan.Generic.Options: [getWithOptions] :: WithOptions (options :: [*]) a -> a
+ Jordan.Generic.Options: data KeepNothingFields
+ Jordan.Generic.Options: data OmitNothingFields
+ Jordan.Generic.Options: fullyQualifyName :: TypeRep -> Text
+ Jordan.Generic.Options: instance GHC.Classes.Eq (f a) => GHC.Classes.Eq (Jordan.Generic.Options.PartOfSum f a)
+ Jordan.Generic.Options: instance GHC.Classes.Eq a => GHC.Classes.Eq (Jordan.Generic.Options.WithOptions options a)
+ Jordan.Generic.Options: instance GHC.Classes.Ord (f a) => GHC.Classes.Ord (Jordan.Generic.Options.PartOfSum f a)
+ Jordan.Generic.Options: instance GHC.Classes.Ord a => GHC.Classes.Ord (Jordan.Generic.Options.WithOptions options a)
+ Jordan.Generic.Options: instance GHC.Generics.Generic (Jordan.Generic.Options.PartOfSum f a)
+ Jordan.Generic.Options: instance GHC.Read.Read (f a) => GHC.Read.Read (Jordan.Generic.Options.PartOfSum f a)
+ Jordan.Generic.Options: instance GHC.Show.Show (f a) => GHC.Show.Show (Jordan.Generic.Options.PartOfSum f a)
+ Jordan.Generic.Options: instance GHC.Show.Show a => GHC.Show.Show (Jordan.Generic.Options.WithOptions options a)
+ Jordan.Generic.Options: newtype WithOptions (options :: [*]) a
+ Jordan.Generic.Options: type Representational (f :: * -> *) = (forall a b. (Coercible a b) => Coercible (f a) (f b) :: Constraint)
+ Jordan.Generic.Options: type family AllNullary cons
+ Jordan.ToJSON.Class: [toJSONOmitNothingFields] :: ToJSONOptions -> Bool
+ Jordan.ToJSON.Class: class GToJSONTuple v
+ Jordan.ToJSON.Class: class SpecifiesToJSONOptions (a :: [*])
+ Jordan.ToJSON.Class: gToJSONTuple :: (GToJSONTuple v, JSONTupleSerializer f) => ToJSONOptions -> f (v a)
+ Jordan.ToJSON.Class: instance (GHC.Generics.Generic a, Jordan.ToJSON.Class.GToJSON (GHC.Generics.Rep a), Data.Typeable.Internal.Typeable a, Jordan.ToJSON.Class.SpecifiesToJSONOptions options) => Jordan.ToJSON.Class.ToJSON (Jordan.Generic.Options.WithOptions options a)
+ Jordan.ToJSON.Class: instance (Jordan.ToJSON.Class.GToJSON f, GHC.TypeLits.KnownSymbol selector) => Jordan.ToJSON.Class.GToJSONObject (GHC.Generics.S1 ('GHC.Generics.MetaSel ('GHC.Maybe.Just selector) su ss ds) f)
+ Jordan.ToJSON.Class: instance (Jordan.ToJSON.Class.GToJSONObject inner, GHC.Generics.Constructor ('GHC.Generics.MetaCons n s 'GHC.Types.True)) => Jordan.ToJSON.Class.GToJSON (GHC.Generics.C1 ('GHC.Generics.MetaCons n s 'GHC.Types.True) inner)
+ Jordan.ToJSON.Class: instance (Jordan.ToJSON.Class.GToJSONTuple inner, GHC.Generics.Constructor ('GHC.Generics.MetaCons n s 'GHC.Types.False)) => Jordan.ToJSON.Class.GToJSON (GHC.Generics.C1 ('GHC.Generics.MetaCons n s 'GHC.Types.False) inner)
+ Jordan.ToJSON.Class: instance (Jordan.ToJSON.Class.GToJSONTuple lhs, Jordan.ToJSON.Class.GToJSONTuple rhs) => Jordan.ToJSON.Class.GToJSONTuple (lhs GHC.Generics.:*: rhs)
+ Jordan.ToJSON.Class: instance (Jordan.ToJSON.Class.ToJSON a, GHC.TypeLits.KnownSymbol selector) => Jordan.ToJSON.Class.GToJSONObject (GHC.Generics.S1 ('GHC.Generics.MetaSel ('GHC.Maybe.Just selector) su ss ds) (GHC.Generics.Rec0 (GHC.Maybe.Maybe a)))
+ Jordan.ToJSON.Class: instance GHC.TypeLits.KnownSymbol name => Jordan.ToJSON.Class.GToJSON (GHC.Generics.C1 ('GHC.Generics.MetaCons name fixity 'GHC.Types.False) GHC.Generics.U1)
+ Jordan.ToJSON.Class: instance GHC.TypeLits.KnownSymbol name => Jordan.ToJSON.Class.GToJSON (Jordan.Generic.Options.PartOfSum (GHC.Generics.C1 ('GHC.Generics.MetaCons name fixity 'GHC.Types.False) GHC.Generics.U1))
+ Jordan.ToJSON.Class: instance Jordan.ToJSON.Class.GToJSON f => Jordan.ToJSON.Class.GToJSONTuple (GHC.Generics.S1 ('GHC.Generics.MetaSel 'GHC.Maybe.Nothing su ss ds) f)
+ Jordan.ToJSON.Class: instance Jordan.ToJSON.Class.SpecifiesToJSONOptions '[]
+ Jordan.ToJSON.Class: instance Jordan.ToJSON.Class.SpecifiesToJSONOptions xs => Jordan.ToJSON.Class.SpecifiesToJSONOptions (Jordan.Generic.Options.KeepNothingFields : xs)
+ Jordan.ToJSON.Class: instance Jordan.ToJSON.Class.SpecifiesToJSONOptions xs => Jordan.ToJSON.Class.SpecifiesToJSONOptions (Jordan.Generic.Options.OmitNothingFields : xs)
+ Jordan.ToJSON.Class: instance Jordan.ToJSON.Class.ToJSON a => Jordan.ToJSON.Class.ToJSON (Data.Map.Internal.Map GHC.Integer.Type.Integer a)
+ Jordan.ToJSON.Class: instance Jordan.ToJSON.Class.ToJSON a => Jordan.ToJSON.Class.ToJSON (Data.Set.Internal.Set a)
+ Jordan.ToJSON.Class: instance Jordan.ToJSON.Class.ToJSON a => Jordan.ToJSON.Class.ToJSON (GHC.Base.NonEmpty a)
+ Jordan.ToJSON.Class: instance Jordan.ToJSON.Class.ToJSON i => Jordan.ToJSON.Class.GToJSON (GHC.Generics.C1 ('GHC.Generics.MetaCons n s 'GHC.Types.False) (GHC.Generics.S1 ('GHC.Generics.MetaSel 'GHC.Maybe.Nothing su ss ds) (GHC.Generics.Rec0 i)))
+ Jordan.ToJSON.Class: nameSerializer :: JSONSerializer f => Text -> f a -> f a
+ Jordan.ToJSON.Class: selected :: Selectable f => f lhs -> f rhs -> f (Either lhs rhs)
+ Jordan.ToJSON.Class: serializeDescribeFieldWith :: JSONObjectSerializer f => Text -> Text -> (forall valueSerializer. JSONSerializer valueSerializer => valueSerializer a) -> f a
+ Jordan.ToJSON.Class: serializeField :: (JSONObjectSerializer f, ToJSON a) => Text -> f a
+ Jordan.ToJSON.Class: serializeFieldWith :: JSONObjectSerializer f => Text -> (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) -> f a
+ Jordan.ToJSON.Class: serializeItem :: (JSONTupleSerializer f, ToJSON a) => f a
+ Jordan.ToJSON.Class: serializeItemWith :: JSONTupleSerializer f => (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) -> f a
+ Jordan.ToJSON.Class: serializeJust :: JSONObjectSerializer f => Text -> (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) -> f (Maybe a)
+ Jordan.ToJSON.Class: specifiedToJSONOptions :: SpecifiesToJSONOptions a => ToJSONOptions
+ Jordan.Types.Internal.AccumE: AccumE :: Either err val -> AccumE err val
+ Jordan.Types.Internal.AccumE: [getAccumE] :: AccumE err val -> Either err val
+ Jordan.Types.Internal.AccumE: instance (GHC.Read.Read a, GHC.Read.Read b) => GHC.Read.Read (Jordan.Types.Internal.AccumE.AccumE a b)
+ Jordan.Types.Internal.AccumE: instance (GHC.Show.Show a, GHC.Show.Show b) => GHC.Show.Show (Jordan.Types.Internal.AccumE.AccumE a b)
+ Jordan.Types.Internal.AccumE: instance Data.Bifunctor.Bifunctor Jordan.Types.Internal.AccumE.AccumE
+ Jordan.Types.Internal.AccumE: instance GHC.Base.Functor (Jordan.Types.Internal.AccumE.AccumE err)
+ Jordan.Types.Internal.AccumE: instance GHC.Base.Monoid err => GHC.Base.Monoid (Jordan.Types.Internal.AccumE.AccumE err a)
+ Jordan.Types.Internal.AccumE: instance GHC.Base.Monoid m => GHC.Base.Alternative (Jordan.Types.Internal.AccumE.AccumE m)
+ Jordan.Types.Internal.AccumE: instance GHC.Base.Semigroup e => GHC.Base.Applicative (Jordan.Types.Internal.AccumE.AccumE e)
+ Jordan.Types.Internal.AccumE: instance GHC.Base.Semigroup e => GHC.Base.Semigroup (Jordan.Types.Internal.AccumE.AccumE e a)
+ Jordan.Types.Internal.AccumE: instance GHC.Generics.Generic (Jordan.Types.Internal.AccumE.AccumE err val)
+ Jordan.Types.Internal.AccumE: newtype AccumE err val
+ Jordan.Types.Internal.AccumE: pattern AccumEL :: err -> AccumE err val
+ Jordan.Types.Internal.AccumE: pattern AccumER :: val -> AccumE err val
+ Jordan.Types.Internal.MergeMap: MergeMap :: Map key val -> MergeMap key val
+ Jordan.Types.Internal.MergeMap: [getMergeMap] :: MergeMap key val -> Map key val
+ Jordan.Types.Internal.MergeMap: instance (GHC.Base.Semigroup val, GHC.Classes.Ord key) => GHC.Base.Monoid (Jordan.Types.Internal.MergeMap.MergeMap key val)
+ Jordan.Types.Internal.MergeMap: instance (GHC.Base.Semigroup val, GHC.Classes.Ord key) => GHC.Base.Semigroup (Jordan.Types.Internal.MergeMap.MergeMap key val)
+ Jordan.Types.Internal.MergeMap: instance (GHC.Classes.Eq key, GHC.Classes.Eq val) => GHC.Classes.Eq (Jordan.Types.Internal.MergeMap.MergeMap key val)
+ Jordan.Types.Internal.MergeMap: instance Data.Foldable.Foldable (Jordan.Types.Internal.MergeMap.MergeMap key)
+ Jordan.Types.Internal.MergeMap: instance Data.Traversable.Traversable (Jordan.Types.Internal.MergeMap.MergeMap key)
+ Jordan.Types.Internal.MergeMap: instance GHC.Base.Functor (Jordan.Types.Internal.MergeMap.MergeMap key)
+ Jordan.Types.Internal.MergeMap: instance GHC.Classes.Ord key => GHC.Exts.IsList (Jordan.Types.Internal.MergeMap.MergeMap key val)
+ Jordan.Types.Internal.MergeMap: mergeSingleton :: k -> v -> MergeMap k v
+ Jordan.Types.Internal.MergeMap: newtype MergeMap key val
+ Jordan.Types.JSONError: ErrorBadArray :: JSONArrayError -> JSONError
+ Jordan.Types.JSONError: ErrorBadObject :: JSONObjectError -> JSONError
+ Jordan.Types.JSONError: ErrorBadTextConstant :: !Text -> !Text -> JSONError
+ Jordan.Types.JSONError: ErrorBadType :: !JSONType -> !JSONType -> JSONError
+ Jordan.Types.JSONError: ErrorChoice :: !Set JSONError -> JSONError
+ Jordan.Types.JSONError: ErrorInvalidJSON :: JSONError
+ Jordan.Types.JSONError: ErrorMesage :: Text -> JSONError
+ Jordan.Types.JSONError: ErrorNoValue :: JSONError
+ Jordan.Types.JSONError: MkJSONArrayError :: Map Integer JSONError -> JSONArrayError
+ Jordan.Types.JSONError: MkJSONObjectError :: Map Text JSONError -> JSONObjectError
+ Jordan.Types.JSONError: [actualText] :: JSONError -> !Text
+ Jordan.Types.JSONError: [actualType] :: JSONError -> !JSONType
+ Jordan.Types.JSONError: [expectedText] :: JSONError -> !Text
+ Jordan.Types.JSONError: [expectedType] :: JSONError -> !JSONType
+ Jordan.Types.JSONError: data JSONError
+ Jordan.Types.JSONError: indexErrors :: JSONArrayError -> Map Integer JSONError
+ Jordan.Types.JSONError: instance Control.DeepSeq.NFData Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance Control.DeepSeq.NFData Jordan.Types.JSONError.JSONError
+ Jordan.Types.JSONError: instance Control.DeepSeq.NFData Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: instance GHC.Base.Monoid Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance GHC.Base.Monoid Jordan.Types.JSONError.JSONError
+ Jordan.Types.JSONError: instance GHC.Base.Monoid Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: instance GHC.Base.Semigroup Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance GHC.Base.Semigroup Jordan.Types.JSONError.JSONError
+ Jordan.Types.JSONError: instance GHC.Base.Semigroup Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: instance GHC.Classes.Eq Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance GHC.Classes.Eq Jordan.Types.JSONError.JSONError
+ Jordan.Types.JSONError: instance GHC.Classes.Eq Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: instance GHC.Classes.Ord Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance GHC.Classes.Ord Jordan.Types.JSONError.JSONError
+ Jordan.Types.JSONError: instance GHC.Classes.Ord Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: instance GHC.Exts.IsList Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance GHC.Exts.IsList Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: instance GHC.Generics.Generic Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance GHC.Generics.Generic Jordan.Types.JSONError.JSONError
+ Jordan.Types.JSONError: instance GHC.Generics.Generic Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: instance GHC.Read.Read Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance GHC.Read.Read Jordan.Types.JSONError.JSONError
+ Jordan.Types.JSONError: instance GHC.Read.Read Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: instance GHC.Show.Show Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance GHC.Show.Show Jordan.Types.JSONError.JSONError
+ Jordan.Types.JSONError: instance GHC.Show.Show Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: instance Jordan.FromJSON.Class.FromJSON Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance Jordan.FromJSON.Class.FromJSON Jordan.Types.JSONError.JSONError
+ Jordan.Types.JSONError: instance Jordan.FromJSON.Class.FromJSON Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: instance Jordan.ToJSON.Class.ToJSON Jordan.Types.JSONError.JSONArrayError
+ Jordan.Types.JSONError: instance Jordan.ToJSON.Class.ToJSON Jordan.Types.JSONError.JSONError
+ Jordan.Types.JSONError: instance Jordan.ToJSON.Class.ToJSON Jordan.Types.JSONError.JSONObjectError
+ Jordan.Types.JSONError: keyValueErrors :: JSONObjectError -> Map Text JSONError
+ Jordan.Types.JSONError: newtype JSONArrayError
+ Jordan.Types.JSONError: newtype JSONObjectError
+ Jordan.Types.JSONError: prettyPrintJSONError :: JSONError -> Text
+ Jordan.Types.JSONError: singleObjectError :: Text -> JSONError -> JSONObjectError
+ Jordan.Types.JSONType: JSONTypeArray :: JSONType
+ Jordan.Types.JSONType: JSONTypeBool :: JSONType
+ Jordan.Types.JSONType: JSONTypeNull :: JSONType
+ Jordan.Types.JSONType: JSONTypeNumber :: JSONType
+ Jordan.Types.JSONType: JSONTypeObject :: JSONType
+ Jordan.Types.JSONType: JSONTypeText :: JSONType
+ Jordan.Types.JSONType: data JSONType
+ Jordan.Types.JSONType: instance Control.DeepSeq.NFData Jordan.Types.JSONType.JSONType
+ Jordan.Types.JSONType: instance GHC.Classes.Eq Jordan.Types.JSONType.JSONType
+ Jordan.Types.JSONType: instance GHC.Classes.Ord Jordan.Types.JSONType.JSONType
+ Jordan.Types.JSONType: instance GHC.Enum.Bounded Jordan.Types.JSONType.JSONType
+ Jordan.Types.JSONType: instance GHC.Enum.Enum Jordan.Types.JSONType.JSONType
+ Jordan.Types.JSONType: instance GHC.Generics.Generic Jordan.Types.JSONType.JSONType
+ Jordan.Types.JSONType: instance GHC.Read.Read Jordan.Types.JSONType.JSONType
+ Jordan.Types.JSONType: instance GHC.Show.Show Jordan.Types.JSONType.JSONType
+ Jordan.Types.JSONType: instance Jordan.FromJSON.Class.FromJSON Jordan.Types.JSONType.JSONType
+ Jordan.Types.JSONType: instance Jordan.ToJSON.Class.ToJSON Jordan.Types.JSONType.JSONType
+ Jordan.Types.JSONValue: JArray :: [JSONValue] -> JSONValue
+ Jordan.Types.JSONValue: JBool :: Bool -> JSONValue
+ Jordan.Types.JSONValue: JNull :: JSONValue
+ Jordan.Types.JSONValue: JNumber :: Scientific -> JSONValue
+ Jordan.Types.JSONValue: JObject :: Map Text JSONValue -> JSONValue
+ Jordan.Types.JSONValue: JText :: Text -> JSONValue
+ Jordan.Types.JSONValue: data JSONValue
+ Jordan.Types.JSONValue: instance GHC.Classes.Eq Jordan.Types.JSONValue.JSONValue
+ Jordan.Types.JSONValue: instance GHC.Classes.Ord Jordan.Types.JSONValue.JSONValue
+ Jordan.Types.JSONValue: instance GHC.Generics.Generic Jordan.Types.JSONValue.JSONValue
+ Jordan.Types.JSONValue: instance GHC.Show.Show Jordan.Types.JSONValue.JSONValue
+ Jordan.Types.JSONValue: instance Jordan.FromJSON.Class.FromJSON Jordan.Types.JSONValue.JSONValue
+ Jordan.Types.JSONValue: instance Jordan.ToJSON.Class.ToJSON Jordan.Types.JSONValue.JSONValue
- Jordan: FromJSONOptions :: SumTypeEncoding -> String -> (String -> String) -> FromJSONOptions
+ Jordan: FromJSONOptions :: SumTypeEncoding -> String -> (String -> String) -> Bool -> FromJSONOptions
- Jordan: ToJSONOptions :: SumTypeEncoding -> String -> (String -> String) -> ToJSONOptions
+ Jordan: ToJSONOptions :: SumTypeEncoding -> String -> (String -> String) -> Bool -> ToJSONOptions
- Jordan: class (Applicative f) => JSONObjectParser f
+ Jordan: class (Applicative f, Representational f) => JSONObjectParser f
- Jordan: class (Divisible f) => JSONObjectSerializer f
+ Jordan: class (Divisible f, Representational f) => JSONObjectSerializer f
- Jordan: class (Functor f, forall a. Monoid (f a)) => JSONParser f
+ Jordan: class (Functor f, forall a. Semigroup (f a), Representational f) => JSONParser f
- Jordan: class (Selectable f) => JSONSerializer f
+ Jordan: class (Selectable f, Representational f) => JSONSerializer f
- Jordan: class (Applicative f) => JSONTupleParser f
+ Jordan: class (Applicative f, Representational f) => JSONTupleParser f
- Jordan: fromJSON :: (FromJSON value, Generic value, GFromJSON (Rep value)) => JSONParser f => f value
+ Jordan: fromJSON :: (FromJSON value, Generic value, GFromJSON (Rep value), Typeable value) => JSONParser f => f value
- Jordan: parseObject :: JSONParser f => Text -> (forall objectParser. JSONObjectParser objectParser => objectParser a) -> f a
+ Jordan: parseObject :: JSONParser f => (forall objectParser. JSONObjectParser objectParser => objectParser a) -> f a
- Jordan: parseObjectStrict :: JSONParser f => Text -> (forall objectParser. JSONObjectParser objectParser => objectParser a) -> f a
+ Jordan: parseObjectStrict :: JSONParser f => (forall objectParser. JSONObjectParser objectParser => objectParser a) -> f a
- Jordan: parseViaAttoparsec :: FromJSON val => ByteString -> Either String val
+ Jordan: parseViaAttoparsec :: forall val. FromJSON val => ByteString -> Either String val
- Jordan: serializeObject :: JSONSerializer f => Text -> (forall objSerializer. JSONObjectSerializer objSerializer => objSerializer a) -> f a
+ Jordan: serializeObject :: JSONSerializer f => (forall objSerializer. JSONObjectSerializer objSerializer => objSerializer a) -> f a
- Jordan: toJSON :: (ToJSON v, Generic v, GToJSON (Rep v)) => JSONSerializer f => f v
+ Jordan: toJSON :: (ToJSON v, Generic v, GToJSON (Rep v), Typeable v) => JSONSerializer f => f v
- Jordan.FromJSON.Attoparsec: parseViaAttoparsec :: FromJSON val => ByteString -> Either String val
+ Jordan.FromJSON.Attoparsec: parseViaAttoparsec :: forall val. FromJSON val => ByteString -> Either String val
- Jordan.FromJSON.Class: FromJSONOptions :: SumTypeEncoding -> String -> (String -> String) -> FromJSONOptions
+ Jordan.FromJSON.Class: FromJSONOptions :: SumTypeEncoding -> String -> (String -> String) -> Bool -> FromJSONOptions
- Jordan.FromJSON.Class: class (Applicative f) => JSONObjectParser f
+ Jordan.FromJSON.Class: class (Applicative f, Representational f) => JSONObjectParser f
- Jordan.FromJSON.Class: class (Functor f, forall a. Monoid (f a)) => JSONParser f
+ Jordan.FromJSON.Class: class (Functor f, forall a. Semigroup (f a), Representational f) => JSONParser f
- Jordan.FromJSON.Class: class (Applicative f) => JSONTupleParser f
+ Jordan.FromJSON.Class: class (Applicative f, Representational f) => JSONTupleParser f
- Jordan.FromJSON.Class: fromJSON :: (FromJSON value, Generic value, GFromJSON (Rep value)) => JSONParser f => f value
+ Jordan.FromJSON.Class: fromJSON :: (FromJSON value, Generic value, GFromJSON (Rep value), Typeable value) => JSONParser f => f value
- Jordan.FromJSON.Class: parseObject :: JSONParser f => Text -> (forall objectParser. JSONObjectParser objectParser => objectParser a) -> f a
+ Jordan.FromJSON.Class: parseObject :: JSONParser f => (forall objectParser. JSONObjectParser objectParser => objectParser a) -> f a
- Jordan.FromJSON.Class: parseObjectStrict :: JSONParser f => Text -> (forall objectParser. JSONObjectParser objectParser => objectParser a) -> f a
+ Jordan.FromJSON.Class: parseObjectStrict :: JSONParser f => (forall objectParser. JSONObjectParser objectParser => objectParser a) -> f a
- Jordan.ToJSON.Class: ToJSONOptions :: SumTypeEncoding -> String -> (String -> String) -> ToJSONOptions
+ Jordan.ToJSON.Class: ToJSONOptions :: SumTypeEncoding -> String -> (String -> String) -> Bool -> ToJSONOptions
- Jordan.ToJSON.Class: class (Divisible f) => JSONObjectSerializer f
+ Jordan.ToJSON.Class: class (Divisible f, Representational f) => JSONObjectSerializer f
- Jordan.ToJSON.Class: class (Selectable f) => JSONSerializer f
+ Jordan.ToJSON.Class: class (Selectable f, Representational f) => JSONSerializer f
- Jordan.ToJSON.Class: class (Divisible f) => JSONTupleSerializer f
+ Jordan.ToJSON.Class: class (Divisible f, Representational f) => JSONTupleSerializer f
- Jordan.ToJSON.Class: serializeObject :: JSONSerializer f => Text -> (forall objSerializer. JSONObjectSerializer objSerializer => objSerializer a) -> f a
+ Jordan.ToJSON.Class: serializeObject :: JSONSerializer f => (forall objSerializer. JSONObjectSerializer objSerializer => objSerializer a) -> f a
- Jordan.ToJSON.Class: toJSON :: (ToJSON v, Generic v, GToJSON (Rep v)) => JSONSerializer f => f v
+ Jordan.ToJSON.Class: toJSON :: (ToJSON v, Generic v, GToJSON (Rep v), Typeable v) => JSONSerializer f => f v
Files
- CHANGELOG.md +15/−1
- jordan.cabal +18/−17
- lib/Jordan.hs +76/−31
- lib/Jordan/FromJSON/Attoparsec.hs +91/−241
- lib/Jordan/FromJSON/Class.hs +286/−125
- lib/Jordan/FromJSON/Internal/Attoparsec.hs +431/−0
- lib/Jordan/FromJSON/Internal/Permutation.hs +153/−0
- lib/Jordan/FromJSON/Internal/UnboxedParser.hs +546/−0
- lib/Jordan/FromJSON/Internal/UnboxedReporting.hs +351/−0
- lib/Jordan/FromJSON/Megaparsec.hs +0/−295
- lib/Jordan/FromJSON/ParseInternal.hs +0/−120
- lib/Jordan/FromJSON/UnboxedReporting.hs +246/−0
- lib/Jordan/Generic/Options.hs +75/−4
- lib/Jordan/ToJSON/Builder.hs +37/−31
- lib/Jordan/ToJSON/Class.hs +235/−98
- lib/Jordan/ToJSON/Text.hs +0/−142
- lib/Jordan/Types/Internal/AccumE.hs +115/−0
- lib/Jordan/Types/Internal/MergeMap.hs +28/−0
- lib/Jordan/Types/JSONError.hs +107/−0
- lib/Jordan/Types/JSONType.hs +23/−0
- lib/Jordan/Types/JSONValue.hs +68/−0
- test/Jordan/FromJSON/MegaparsecSpec.hs +0/−59
- test/Jordan/FromJSON/UnboxedReportingSpec.hs +25/−0
- test/Jordan/RoundTripSpec.hs +195/−94
- test/Jordan/SpecDefs.hs +153/−85
- test/Jordan/ToJSON/TextSpec.hs +0/−85
- test/JordanSpec.hs +5/−6
CHANGELOG.md view
@@ -1,5 +1,19 @@ # Revision history for jordan -## 0.1.0.0 -- YYYY-mm-dd+## 0.2.0.0 - 2022-07-04 +* Removed support for serializing to `Text`.+* Removed support for parsing via Megaparsec.+* Added support for parsing directly to a value or an error report, via a custom unbox-sum-based parser type.+* Added `parseFieldWithDefault`.+* Added `serializeJust`.+* Changed the methods of `JSONObjectSerializer` and `JSONTupleSerializer` to be more consistent with the rest of the library.+* Got rid of mandatory identifiers for object parsers.+* Got rid of mandatory identifiers for object serializers.+* Improved Generic Deriving Mechanisms.+* Lots of optimizations.++## 0.1.0.0 -- ???+ * First version. Released on an unsuspecting world.+* I forgot to document this originally.
jordan.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: jordan-version: 0.1.0.0+version: 0.2.0.0 synopsis: JSON with Structure -- A longer description of the package.@@ -38,30 +38,32 @@ , bytestring >= 0.10.8.1 && <0.12 , containers >= 0.6.2 && <0.7 , scientific >= 0.3.7 && <0.4- , text >= 1.2.3.0 && <1.3- , megaparsec >= 9.1.0 && <9.2+ , text >= 1.2.3.0 && <2.1 , contravariant >= 1.5.5 && <1.6 , parser-combinators >= 1.3.0 && < 1.4+ , deepseq >= 1.4 && <= 1.5+ , ghc-prim >= 0.6.1 && <0.9+ , base >= 4.14.1 && <4.17 library import: build-deps exposed-modules: Jordan.FromJSON.Class- , Jordan.FromJSON.Megaparsec , Jordan.FromJSON.Attoparsec- , Jordan.FromJSON.ParseInternal+ , Jordan.FromJSON.Internal.Attoparsec+ , Jordan.FromJSON.Internal.UnboxedParser+ , Jordan.FromJSON.Internal.Permutation+ , Jordan.FromJSON.Internal.UnboxedReporting+ , Jordan.FromJSON.UnboxedReporting+ , Jordan.Types.Internal.MergeMap+ , Jordan.Types.Internal.AccumE+ , Jordan.Types.JSONValue+ , Jordan.Types.JSONType+ , Jordan.Types.JSONError , Jordan.ToJSON.Class- , Jordan.ToJSON.Text , Jordan.ToJSON.Builder , Jordan.Generic.Options , Jordan-- -- Modules included in this library but not exported.- -- other-modules:-- -- LANGUAGE extensions used by modules in this package.- -- other-extensions:- build-depends: base ^>=4.14.1.0 hs-source-dirs: lib default-language: Haskell2010 @@ -75,14 +77,13 @@ , QuickCheck , quickcheck-text other-modules:- Jordan.FromJSON.MegaparsecSpec- , Jordan.FromJSON.AttoparsecSpec+ Jordan.FromJSON.AttoparsecSpec+ , Jordan.FromJSON.UnboxedReportingSpec , Jordan.SpecDefs- , Jordan.ToJSON.TextSpec , Jordan.ToJSON.BuilderSpec , Jordan.RoundTripSpec default-language: Haskell2010 type: exitcode-stdio-1.0 hs-source-dirs: test main-is: JordanSpec.hs- build-depends: base ^>=4.14.1.0+ ghc-options: -fprint-potential-instances
lib/Jordan.hs view
@@ -2,39 +2,84 @@ -- -- Has all functionality for parsing and serializing JSON. module Jordan- ( -- * JSON Parsing- -- ** Concretely- parseViaMegaparsec- , parseViaAttoparsec- , runParserViaAttoparsec- , runParserViaMegaparsec- , attoparsecParser- , megaparsecParser- -- ** Abstractly- , FromJSON (..)- , JSONParser (..)- , JSONObjectParser (..)- , JSONTupleParser (..)- -- *** Generically- , gFromJSON- , FromJSONOptions (..)- -- * JSON Serialization- -- ** Concretely- , toJSONAsBuilder- , toJSONViaBuilder- , toJSONText- -- ** Abstractly- , ToJSON (..)- , JSONSerializer (..)- , JSONObjectSerializer (..)- -- *** Generically- , gToJSON- , ToJSONOptions (..)- ) where+ ( -- * JSON Parsing + -- ** Abstractly+ FromJSON (..),+ JSONParser (..),+ JSONObjectParser (..),+ JSONTupleParser (..),++ -- ** Concretely++ -- *** Via Attoparsec+ -- $viaAP+ parseViaAttoparsec,+ parseViaAttoparsecWith,+ attoparsecParser,+ attoparsecParserFor,++ -- *** With Error Reporting+ -- $withReport+ parseOrReport,+ parseOrReportWith,++ -- *** Generically+ gFromJSON,+ FromJSONOptions (..),++ -- * JSON Serialization++ -- ** Abstractly+ ToJSON (..),+ JSONSerializer (..),+ JSONObjectSerializer (..),++ -- **** Re-Exports for Serialization+ Contravariant (..),+ Divisible (..),+ Selectable (..),++ -- ** Concretely+ toJSONAsBuilder,+ toJSONViaBuilder,++ -- *** Generically+ gToJSON,+ ToJSONOptions (..),++ -- * Parsing or Serializing Arbitrary JSON+ JSONValue (..),++ -- * Newtypes for DerivingVia+ WithOptions (..),+ OmitNothingFields,+ KeepNothingFields,+ )+where++import Data.Functor.Contravariant+import Data.Functor.Contravariant.Divisible import Jordan.FromJSON.Attoparsec import Jordan.FromJSON.Class-import Jordan.FromJSON.Megaparsec+import Jordan.FromJSON.UnboxedReporting+import Jordan.Generic.Options import Jordan.ToJSON.Builder (toJSONAsBuilder, toJSONViaBuilder) import Jordan.ToJSON.Class-import Jordan.ToJSON.Text (toJSONText)+import Jordan.Types.JSONValue (JSONValue (..))++-- $viaAP+--+-- These parsers use the excellent Attoparsec library to do their work.+-- This means that they're quite fast, but that they also provide less-than-ideal error messages.+-- You should use these when speed is needed, or when you're reasonably certain that nobody will make a mistake.+-- APIs intended only for internal use, for example.++-- $withReport+--+-- These parsers parse to either a value or an *error report*, which is a detailed report of what exactly what wrong.+-- This uses a roll-our-own parsing library based on *unboxed sums*.+-- It's been tested via QuickCheck, but it is doing some spooky-scary raw pointer opertions.+--+-- This is a bit slower than the attoparsec parser, but *much* better at error handling.+-- Use it for external-facing APIs---assuming that you trust my ability to write primops.
lib/Jordan/FromJSON/Attoparsec.hs view
@@ -1,276 +1,115 @@+{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+ -- | Implementation of FromJSON parsers via Attoparsec. -- -- This module does not construct intermediate data structures like maps or key-value lists, -- and instead uses permutation parsers in order to parse your data structure directly.+-- This means that it is pretty fast!+-- However, you also get basically *zero* error reporting, which is generally not what you want. module Jordan.FromJSON.Attoparsec- ( convertParserToAttoparsecParser- , runParserViaAttoparsec- , parseViaAttoparsec- , attoparsecParser- ) where+ ( attoparsecParserFor,+ parseViaAttoparsecWith,+ parseViaAttoparsec,+ attoparsecParser,+ )+where -import Control.Applicative (Alternative(..))+import Control.Applicative (Alternative (..))+import Control.Monad (void, when) import Data.Attoparsec.ByteString ((<?>))+import qualified Data.Attoparsec.ByteString as A import qualified Data.Attoparsec.ByteString as AP import qualified Data.Attoparsec.ByteString.Char8 as CH+import qualified Data.Attoparsec.Combinator as AC import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Unsafe as B import Data.Char (chr, digitToInt, isControl, isHexDigit, ord) import Data.Functor (void, ($>))-import Data.Monoid (Alt(..))+import Data.Monoid (Alt (..)) import Data.Scientific (Scientific)+import qualified Data.Scientific as Sci+import qualified Data.Scientific as Scientific import qualified Data.Text as Text import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Debug.Trace import Jordan.FromJSON.Class-import Jordan.FromJSON.ParseInternal+import Jordan.FromJSON.Internal.Attoparsec+import Jordan.FromJSON.Internal.Permutation import Numeric (showHex)-import qualified Text.Megaparsec as Text -newtype ObjectParser a- = ObjectParser- { runObjectParser :: Permutation AP.Parser a }+newtype ObjectParser a = ObjectParser+ {runObjectParser :: Permutation AP.Parser a} deriving (Functor, Applicative) -newtype ArrayParser a- = ArrayParser- { runArrayParser :: AP.Parser a }+type role ArrayParser representational++data ArrayParser a+ = ParseNoEffect a+ | ParseWithEffect (AP.Parser a) deriving (Functor) instance Applicative ArrayParser where- pure = ArrayParser . pure- f <*> a = ArrayParser $ do- f' <- runArrayParser f- comma- a' <- runArrayParser a- pure $ f' a'--skipSpace :: AP.Parser ()-skipSpace = AP.skipWhile isSpace- where- isSpace = \case- 32 -> True- 10 -> True- 13 -> True- 9 -> True- _ -> False--lexeme :: AP.Parser a -> AP.Parser a-lexeme a = a <* skipSpace--label :: String -> AP.Parser a -> AP.Parser a-label l p = p <?> l--parseAnyField :: AP.Parser ()-parseAnyField = label "junk field" $ void $ do- lexeme parseJSONText- labelSep- lexeme anyDatum--junkFieldAtEnd :: AP.Parser ()-junkFieldAtEnd = void $ do- comma- parseAnyField `AP.sepBy` comma--comma :: AP.Parser ()-comma = label "comma character" $ void $ lexeme (AP.string ",")--quotation :: AP.Parser ()-quotation = label "quotation mark" $ void $ AP.word8 34--parseJSONText :: AP.Parser Text.Text-parseJSONText = label "JSON text" $ do- quotation- innerText--innerText :: AP.Parser Text.Text-innerText = do- chunk <- AP.takeWhile $ \char -> char /= 92 && char /= 34- l <- AP.peekWord8- case l of- Nothing -> fail "string without end"- Just 34 -> do- AP.anyWord8- pure $ decodeUtf8 chunk- Just 92 -> do- AP.anyWord8- r <- label "escape value" parseEscape- rest <- innerText- pure $ decodeUtf8 chunk <> r <> rest- Just _ -> fail "IMPOSSIBLE"--parseEscape :: AP.Parser Text.Text-parseEscape- = quote- <|> backslash- <|> solidus- <|> backspace- <|> formfeed- <|> linefeed- <|> carriage- <|> tab- <|> escaped- where- backslash = AP.string "\\" $> "\\"- quote = AP.string "\"" $> "\""- solidus = AP.string "/" $> "/"- backspace = AP.string "b" $> "\b"- formfeed = AP.string "f" $> "\f"- linefeed = AP.string "n" $> "\n"- carriage = AP.string "r" $> "\r"- tab = AP.string "t" $> "\t"- escaped = do- AP.string "u"- a <- parseHexDigit- b <- parseHexDigit- c <- parseHexDigit- d <- parseHexDigit- let s = (((a * 16) + b) * 16 + c) * 16 + d- pure $ Text.pack [chr s]--parseHexDigit :: AP.Parser Int-parseHexDigit = label "hex digit" (digitToInt <$> CH.satisfy isHexDigit)--parseCharInText :: Char -> AP.Parser ()-parseCharInText a = parseLit a <|> escaped a- where- parseLit :: Char -> AP.Parser ()- parseLit = \case- '\\' -> void $ AP.string "\\\\"- '"' -> void $ AP.string "\\\""- '/' -> void $ AP.string "/" <|> AP.string "\\/"- '\b' -> void $ AP.string "\\b"- '\f' -> void $ AP.string "\\f"- '\n' -> void $ AP.string "\\n"- '\r' -> void $ AP.string "\\r"- '\t' -> void $ AP.string "\\t"- a -> if isControl a then empty else void $ AP.string $ encodeUtf8 $ Text.singleton a- escaped :: Char -> AP.Parser ()- escaped a = void $ AP.string $ encodeUtf8 $ Text.pack $ withEscaped $ (showHex $ ord a) []- withEscaped :: String -> String- withEscaped a@[_] = "\\u000" <> a- withEscaped a@[_,_] = "\\u00" <> a- withEscaped a@[_,_,_] = "\\u0" <> a- withEscaped r = "\\u" <> r--objectKey :: Text.Text -> AP.Parser ()-objectKey k = lexeme $ do- quotation- Text.foldr (\c a -> parseCharInText c *> a) (pure ()) k- quotation- pure ()--startObject :: AP.Parser ()-startObject- = label "object starting brace ('{')"- $ lexeme- $ void- $ AP.word8 123--endObject :: AP.Parser ()-endObject- = label "object ending brace ('}')"- $ lexeme- $ void- $ AP.word8 125--inObjectBraces :: AP.Parser a -> AP.Parser a-inObjectBraces interior = startObject *> interior <* endObject--startArray :: AP.Parser ()-startArray- = label "array starting brace ('[')"- $ lexeme- $ void- $ AP.word8 91--endArray :: AP.Parser ()-endArray- = label "array ending brace (']')"- $ lexeme- $ void- $ AP.word8 93--labelSep :: AP.Parser ()-labelSep = label "key-value separator (':')" $ void $ lexeme $ AP.string ":"--anyDatum :: AP.Parser ()-anyDatum = lexeme inner- where- inner- = runAttoparsecParser parseNull- <|> void (runAttoparsecParser parseBool)- <|> void (runAttoparsecParser parseText)- <|> void (runAttoparsecParser parseNumber)- <|> void (runAttoparsecParser parseBool)- <|> anyObject- <|> anyArray--anyArray :: AP.Parser ()-anyArray = label "ignored array" $ void $ do- startArray- anyDatum `AP.sepBy` comma- endArray--number :: AP.Parser Scientific-number = CH.scientific--anyObject :: AP.Parser ()-anyObject = label "ignored object" $ void $ do- startObject- flip AP.sepBy comma $ do- parseJSONText- labelSep- anyDatum- endObject--parseObjectField- :: Text.Text- -> AP.Parser a- -> AP.Parser a-parseObjectField t f = do- objectKey t- labelSep- lexeme f+ pure = ParseNoEffect+ f <*> a = case f of+ ParseNoEffect fab -> case a of+ ParseNoEffect a' -> ParseNoEffect (fab a')+ ParseWithEffect pa -> ParseWithEffect (fab <$> pa)+ ParseWithEffect pa -> case a of+ ParseNoEffect a' -> ParseWithEffect (fmap ($ a') pa)+ ParseWithEffect pa' -> ParseWithEffect $ do+ f' <- pa+ comma+ f' <$> pa' -parseDictField- :: AP.Parser a- -> AP.Parser (Text.Text, a)-parseDictField p = do- key <- parseJSONText- labelSep- val <- p- pure (key, val)+runArrayParser :: ArrayParser a -> AP.Parser a+runArrayParser (ParseNoEffect a) = pure a+runArrayParser (ParseWithEffect eff) = eff instance JSONObjectParser ObjectParser where- parseFieldWith label- = ObjectParser- . asPermutation- . parseObjectField label- . runAttoparsecParser+ parseFieldWith label parser =+ ObjectParser $+ asPermutation $+ parseObjectField+ label+ (runAttoparsecParser parser)+ {-# INLINE parseFieldWith #-}+ parseFieldWithDefault f = \(AttoparsecParser parseField) def ->+ ObjectParser $+ asPermutationWithDefault (parseObjectField f (parseField <?> ("field " <> show f))) def -newtype AttoparsecParser a- = AttoparsecParser- { runAttoparsecParser :: AP.Parser a }+newtype AttoparsecParser a = AttoparsecParser+ {runAttoparsecParser :: AP.Parser a} deriving (Functor) deriving (Semigroup, Monoid) via (Alt AP.Parser a) instance JSONTupleParser ArrayParser where- consumeItemWith = ArrayParser . runAttoparsecParser+ consumeItemWith = \parser -> ParseWithEffect $ runAttoparsecParser parser instance JSONParser AttoparsecParser where- parseObject _ p = AttoparsecParser $ inObjectBraces $ do- r <- wrapEffect parseAnyField comma $ runObjectParser p- label "junk object fields at the end of a parsed object" $ many junkFieldAtEnd- pure r- parseDictionary parse = AttoparsecParser $ inObjectBraces $ do- parseDictField (runAttoparsecParser parse) `AP.sepBy` comma+ parseObject = \parser -> AttoparsecParser $+ label "Object" $ do+ startObject+ r <- wrapEffect (parseAnyField <?> "ignored field in the middle of an object") comma $ runObjectParser parser+ objectEndWithJunk+ pure r+ {-# INLINE parseObject #-}+ parseDictionary parse = AttoparsecParser $+ inObjectBraces $ do+ parseDictField (runAttoparsecParser parse) `AP.sepBy` comma parseTextConstant c = AttoparsecParser (objectKey c <?> "text constant" <> Text.unpack c)+ {-# INLINE parseTextConstant #-} parseText = AttoparsecParser parseJSONText+ {-# INLINE parseText #-} parseNumber = AttoparsecParser number+ {-# INLINE parseNumber #-} validateJSON v = AttoparsecParser $ do r <- runAttoparsecParser v case r of@@ -281,27 +120,38 @@ r <- runArrayParser ap lexeme $ AP.word8 93 pure r+ {-# INLINE parseTuple #-} parseArrayWith jp = AttoparsecParser $ do startArray r <- lexeme (runAttoparsecParser jp) `AP.sepBy` comma <?> "array items" endArray pure r- parseBool = AttoparsecParser $ lexeme $- (AP.string "true" $> True) <|> (AP.string "false" $> False)+ {-# INLINE parseArrayWith #-}+ parseBool =+ AttoparsecParser $+ lexeme $+ (AP.string "true" $> True) <|> (AP.string "false" $> False) parseNull = AttoparsecParser $ lexeme (AP.string "null" $> ())+ nameParser l = \(AttoparsecParser a) ->+ AttoparsecParser $+ label ("Parser '" <> Text.unpack l <> "'") a -- | Convert an abstract JSON parser to an Attoparsec Parser. -- This function will skip leading whitespace.-convertParserToAttoparsecParser :: (forall parser. JSONParser parser => parser a) -> AP.Parser a-convertParserToAttoparsecParser = (skipSpace *>) . runAttoparsecParser+attoparsecParserFor :: (forall parser. JSONParser parser => parser a) -> AP.Parser a+attoparsecParserFor = \parser -> (skipSpace *>) $ runAttoparsecParser parser+{-# INLINE attoparsecParserFor #-} -runParserViaAttoparsec :: (forall parser. JSONParser parser => parser a) -> ByteString -> Either String a-runParserViaAttoparsec p = AP.parseOnly (convertParserToAttoparsecParser p)+parseViaAttoparsecWith :: (forall parser. JSONParser parser => parser a) -> ByteString -> Either String a+parseViaAttoparsecWith p = AP.parseOnly (attoparsecParserFor p)+{-# INLINE parseViaAttoparsecWith #-} -- | Parse a ByteString via an Attoparsec Parser.-parseViaAttoparsec :: (FromJSON val) => ByteString -> Either String val-parseViaAttoparsec = AP.parseOnly (skipSpace *> runAttoparsecParser fromJSON)+parseViaAttoparsec :: forall val. (FromJSON val) => ByteString -> Either String val+parseViaAttoparsec = parseViaAttoparsecWith (fromJSON @val)+{-# INLINE parseViaAttoparsec #-} -- | Get an Attoparsec parser for a particular JSON-parsable value. attoparsecParser :: (FromJSON val) => AP.Parser val attoparsecParser = runAttoparsecParser fromJSON+{-# INLINE attoparsecParser #-}
lib/Jordan/FromJSON/Class.hs view
@@ -1,9 +1,11 @@+{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE RankNTypes #-}@@ -11,133 +13,219 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-}+ -- | Parse JSON using finally-tagless style. -- -- This provides JSON parsing as an abstract interface. -- This interface provides a way to parse JSON that is *inspectable* -- and has some nice properties: for example, we can use it to build a parser that -- directly parses your data structure, without building some intermediate value type!-module Jordan.FromJSON.Class- where+module Jordan.FromJSON.Class where -import Control.Applicative (Alternative(..))+import Control.Applicative (Alternative (..))+import Data.Coerce import Data.Functor (($>))+import qualified Data.Int as I+import Data.List.NonEmpty as NE import qualified Data.Map.Strict as Map import qualified Data.Monoid as Monoid-import Data.Proxy (Proxy(..))+import Data.Proxy (Proxy (..)) import qualified Data.Ratio as Ratio import Data.Scientific (Scientific) import qualified Data.Semigroup as Semigroup import qualified Data.Set as Set import qualified Data.Text as T+import qualified Data.Text.Read as TR import Data.Typeable import GHC.Generics+import GHC.TypeLits (KnownSymbol, symbolVal) import Jordan.Generic.Options -- | A class for parsing JSON objects.-class (Applicative f) => JSONObjectParser f where+class (Applicative f, Representational f) => JSONObjectParser f where -- | Parse an object field with a given label, using a parser. -- -- Note: in order to enable the generation of better documentation, use 'parseField' instead if at all possible!- parseFieldWith- :: T.Text- -- ^ Label of the field.+ parseFieldWith ::+ -- | Label of the field. -- Will be parsed into escaped text, if need be.- -> (forall valueParser. JSONParser valueParser => valueParser a)- -- ^ How to parse the field.+ T.Text ->+ -- | How to parse the field. -- Note the forall in this type signature: you cannot have this be specific to -- any particular implementation of parsing, to keep the parsing of a JSON abstract.- -> f a- parseField- :: (FromJSON v)- => T.Text- -> f v+ (forall valueParser. JSONParser valueParser => valueParser a) ->+ f a++ parseDescribeFieldWith ::+ -- | Field key to parse+ T.Text ->+ -- | Description of the field+ T.Text ->+ -- | Parser for the field+ (forall valueParser. JSONParser valueParser => valueParser a) ->+ f a+ parseDescribeFieldWith field _ = parseFieldWith field+ parseField ::+ (FromJSON v) =>+ T.Text ->+ f v parseField t = parseFieldWith t fromJSON+ {-# INLINE parseField #-}+ parseDescribeField ::+ (FromJSON v) =>+ T.Text ->+ T.Text ->+ f v+ parseDescribeField key desc = parseDescribeFieldWith key desc fromJSON+ parseFieldWithDefault ::+ -- | Label of the field.+ T.Text ->+ -- | Parse the value from the field+ (forall valueParser. JSONParser valueParser => valueParser a) ->+ -- | Default value for the field+ a ->+ -- | Field in the object.+ f a+ parseDescribeFieldWithDefault ::+ -- | Label of the field+ T.Text ->+ -- | Description of the field+ T.Text ->+ -- | Parser for the field+ (forall valueParser. JSONParser valueParser => valueParser a) ->+ a ->+ f a+ parseDescribeFieldWithDefault field _ = parseFieldWithDefault field -- | A class for parsing JSON arrays.-class (Applicative f) => JSONTupleParser f where+class (Applicative f, Representational f) => JSONTupleParser f where -- | Use a JSON parser to consume a single item of an array, then move onto the next one. -- -- Note: you should prefer 'consumeItem' as it enables better documentation generation.- consumeItemWith- :: (forall valueParser. JSONParser valueParser => valueParser a)- -> f a+ consumeItemWith ::+ (forall valueParser. JSONParser valueParser => valueParser a) ->+ f a+ -- | Consume a single array item.- consumeItem- :: (FromJSON v)- => f v+ consumeItem ::+ (FromJSON v) =>+ f v consumeItem = consumeItemWith fromJSON -- | Abstract class representing various parsers. -- -- All parsers must have a Monoid instance that represents choice with failure as the identity.-class (Functor f, forall a. Monoid (f a)) => JSONParser f where- parseObject- :: T.Text- -- ^ A label for the object.- -- This label should, as much as possible, be "globally unique" in some way.- -- This will enable better generation of documentation.- -> (forall objectParser. JSONObjectParser objectParser => objectParser a)- -- ^ Instructions on how to parse the object.+class (Functor f, forall a. Semigroup (f a), Representational f) => JSONParser f where+ parseObject ::+ -- | Instructions on how to parse the object. -- Note that the actual implementation is kept abstract: you can only use methods found in JSONObjectParser, or -- combinators of those methods. -- This ensures that we can generate the proper parser in all cases.- -> f a+ (forall objectParser. JSONObjectParser objectParser => objectParser a) ->+ f a+ -- | Parse an object where you are okay if we parse strictly, IE, do not allow extra fields. -- This sometimes enables us to generate parsers that run faster.- parseObjectStrict- :: T.Text- -> (forall objectParser. JSONObjectParser objectParser => objectParser a)- -> f a+ parseObjectStrict ::+ (forall objectParser. JSONObjectParser objectParser => objectParser a) ->+ f a parseObjectStrict = parseObject+ -- | Parse a dictionary of key-value pairs.- parseDictionary- :: (forall jsonParser. JSONParser jsonParser => jsonParser a)- -> f [(T.Text, a)]+ parseDictionary ::+ (forall jsonParser. JSONParser jsonParser => jsonParser a) ->+ f [(T.Text, a)] -- | Parse a text field.- parseText- :: f T.Text- parseTextConstant- :: T.Text- -> f ()+ parseText ::+ f T.Text++ parseTextConstant ::+ T.Text ->+ f () parseTextConstant t = validateJSON (validated <$> parseText) where validated q | q == t = Right () | otherwise = Left $ T.pack "Expected :" <> q+ -- | Use a tuple parser to parse an array.- parseTuple- :: (forall arrayParser. JSONTupleParser arrayParser => arrayParser o)- -> f o- parseArray- :: (FromJSON a)- => f [a]+ parseTuple ::+ (forall arrayParser. JSONTupleParser arrayParser => arrayParser o) ->+ f o++ parseArray ::+ (FromJSON a) =>+ f [a] parseArray = parseArrayWith fromJSON- parseArrayWith- :: (forall jsonParser. JSONParser jsonParser => jsonParser a)- -> f [a]- parseNumber- :: f Scientific- parseNull- :: f ()- parseBool- :: f Bool- validateJSON- :: f (Either T.Text a)- -> f a+ parseArrayWith ::+ (forall jsonParser. JSONParser jsonParser => jsonParser a) ->+ f [a]+ parseNumber ::+ f Scientific+ parseInteger ::+ f Integer+ parseInteger = round <$> parseNumber+ parseNull ::+ f ()+ parseBool ::+ f Bool+ validateJSON ::+ f (Either T.Text a) ->+ f a + -- | Give a parser a unique name.+ -- May be used for documentation.+ nameParser ::+ T.Text ->+ f a ->+ f a+ nameParser _ a = a++ -- | Add information about the format of a particular parser.+ addFormat ::+ T.Text ->+ f a ->+ f a+ addFormat _ a = a+ -- | A class to provide the canonical way to parse a JSON. -- This class uses finally tagless tyle to keep the instructions for parsing abstract. -- This allows us to automatically generate documentation, and to generate parsers that do not use intermediate structures. -- -- This class is derivable generically, and will generate a \"nice\" format. -- In my opinion, at least.+--+-- If you want to customize this JSON, the newtype 'WithOptions' can be helpful, as it allows you to specify options for the generic serialization.+-- Unfortunately, due to a weird GHC quirk, you need to use it with @ -XStandaloneDeriving @ as well as @ -XDerivingVia @.+-- That is, you should write:+--+--+-- @+-- data PersonFilter = PersonFilter { filterFirstName :: Maybe Text, filterLastName :: Maybe Text }+-- deriving (Show, Read, Eq, Ord, Generic)+--+-- deriving via (WithOptions '[KeepNothingFields] PersonFilter) instance (FromJSON PersonFilter)+-- @+--+-- === __Laws__+--+-- This instance is lawless, unless 'Jordan.ToJSON.Class.ToJSON' is also defined for this type.+-- In that case, the representation parsed by 'FromJSON' should match that of the representation serialized by+-- 'Jordan.ToJSON.Class.ToJSON'. class FromJSON value where fromJSON :: (JSONParser f) => f value- default fromJSON :: (Generic value, GFromJSON (Rep value)) => (JSONParser f => f value)- fromJSON = to <$> gFromJSON @(Rep value) defaultOptions+ {-# INLINE fromJSON #-}+ default fromJSON :: (Generic value, GFromJSON (Rep value), Typeable value) => (JSONParser f => f value)+ fromJSON = to <$> gFromJSON @(Rep value) defaultOptions {fromJSONBaseName = bn}+ where+ bn = T.unpack $ fullyQualifyName $ typeRep (Proxy :: Proxy value) +instance (Generic a, GFromJSON (Rep a), Typeable a, SpecifiesFromJSONOptions options) => FromJSON (WithOptions options a) where+ fromJSON = WithOptions . to <$> gFromJSON @(Rep a) (specifiedFromJSONOptions @options) {fromJSONBaseName = bn}+ where+ bn = T.unpack $ fullyQualifyName $ typeRep (Proxy :: Proxy a)+ instance FromJSON () where fromJSON = parseNull @@ -148,7 +236,7 @@ fromJSON = T.unpack <$> parseText instance (FromJSON a) => FromJSON (Maybe a) where- fromJSON = (Nothing <$ parseNull) <> (Just <$> fromJSON)+ fromJSON = (Just <$> fromJSON) <> (parseNull $> Nothing) -- | Right-biased: will try to parse a 'Right' value first. instance (FromJSON l, FromJSON r) => FromJSON (Either l r) where@@ -161,26 +249,32 @@ fromJSON = parseText instance FromJSON Int where- fromJSON = fmap round parseNumber+ fromJSON = fromInteger <$> parseInteger instance FromJSON Float where- fromJSON = realToFrac <$> parseNumber+ fromJSON = addFormat "float" $ realToFrac <$> parseNumber instance FromJSON Double where- fromJSON = realToFrac <$> parseNumber+ fromJSON = addFormat "double" $ realToFrac <$> parseNumber +instance FromJSON I.Int32 where+ fromJSON = addFormat "int32" $ fromInteger <$> parseInteger++instance FromJSON I.Int64 where+ fromJSON = addFormat "int64" $ fromInteger <$> parseInteger+ instance FromJSON Integer where- fromJSON = fmap round parseNumber+ fromJSON = parseInteger instance FromJSON Scientific where fromJSON = parseNumber -instance forall a. (Integral a, FromJSON a, Typeable a) => FromJSON (Ratio.Ratio a) where- fromJSON = parseObject objName $- (Ratio.%) <$> parseField "num" <*> parseField "denom"- where- objName = T.pack $ tyName <> ".Ratio"- tyName = (tyConModule <> const "." <> tyConName) $ typeRepTyCon $ typeRep (Proxy :: Proxy a)+instance forall a. (Integral a, FromJSON a) => FromJSON (Ratio.Ratio a) where+ fromJSON =+ parseObject $+ (Ratio.%)+ <$> parseDescribeField "num" "numerator of the ratio"+ <*> parseDescribeField "denom" "denominator of the ratio" instance FromJSON a => FromJSON (Monoid.Dual a) where fromJSON = Monoid.Dual <$> fromJSON@@ -228,95 +322,162 @@ instance FromJSON a => FromJSON (Map.Map T.Text a) where fromJSON = foldMap (uncurry Map.singleton) <$> parseDictionary fromJSON -data FromJSONOptions- = FromJSONOptions- { fromJSONEncodeSums :: SumTypeEncoding- , fromJSONBaseName :: String- , convertEnum :: String -> String+instance FromJSON a => FromJSON (Map.Map Integer a) where+ fromJSON = foldMap toSingleDict <$> parseDictionary fromJSON+ where+ toSingleDict (k, v) = case TR.signed TR.decimal k of+ Left s -> mempty+ Right (i, rest) -> if rest == mempty then Map.singleton i v else mempty++instance (FromJSON a) => FromJSON (NE.NonEmpty a) where+ fromJSON = validateJSON $ fmap toNonEmpty parseArray+ where+ toNonEmpty a = case NE.nonEmpty a of+ Nothing -> Left "Empty list"+ Just a -> pure a++data FromJSONOptions = FromJSONOptions+ { fromJSONEncodeSums :: SumTypeEncoding,+ fromJSONBaseName :: String,+ convertEnum :: String -> String,+ fromJSONOmitNothingFields :: Bool } deriving (Generic) defaultOptions :: FromJSONOptions-defaultOptions = FromJSONOptions TagInField "" id+defaultOptions = FromJSONOptions TagInField "" id True +class SpecifiesFromJSONOptions (a :: [*]) where+ specifiedFromJSONOptions :: FromJSONOptions++instance SpecifiesFromJSONOptions '[] where+ specifiedFromJSONOptions = defaultOptions++instance+ (SpecifiesFromJSONOptions xs) =>+ SpecifiesFromJSONOptions (OmitNothingFields ': xs)+ where+ specifiedFromJSONOptions = (specifiedFromJSONOptions @xs) {fromJSONOmitNothingFields = True}++instance+ (SpecifiesFromJSONOptions xs) =>+ SpecifiesFromJSONOptions (KeepNothingFields ': xs)+ where+ specifiedFromJSONOptions =+ (specifiedFromJSONOptions @xs) {fromJSONOmitNothingFields = False}+ addName :: String -> FromJSONOptions -> FromJSONOptions-addName s d = d { fromJSONBaseName = fromJSONBaseName d <> s }+addName s d = d {fromJSONBaseName = fromJSONBaseName d <> s} class GFromJSON v where gFromJSON :: (JSONParser f) => FromJSONOptions -> f (v a) +-- | Top-level metadata is ignored. instance (FromJSON c) => GFromJSON (K1 i c) where gFromJSON _ = K1 <$> fromJSON +-- | Datatype metadata: we name the overall datatype with the baseName+-- provided in the options, then serialize the inner information. instance (GFromJSON f, Datatype t) => GFromJSON (D1 t f) where- gFromJSON opts = M1 <$> gFromJSON (addName name opts)- where- name = moduleName s <> "." <> datatypeName s- s :: D1 t f a- s = undefined+ gFromJSON opts = nameParser (T.pack (fromJSONBaseName opts)) $ M1 <$> gFromJSON opts -instance {-# OVERLAPPABLE #-} forall c i. (GFromJSONObject i, Constructor c) => GFromJSON (C1 c i) where- gFromJSON opts = M1 <$> parseObject (T.pack name) (gFromJSONObject opts)+-- | If we have a constructor with arguments, and those arguments+-- do not have selectors (IE, this is not a record), then we should parse as a tuple.+instance+ {-# OVERLAPPABLE #-}+ (GFromJSONTuple inner, KnownSymbol n) =>+ GFromJSON (C1 (MetaCons n s 'False) inner)+ where+ gFromJSON opts = nameParser objName $ M1 <$> parseTuple (gFromJSONTuple opts) where- name = fromJSONBaseName opts <> "." <> conName n- n :: C1 c i a- n = undefined--instance {-# OVERLAPS #-} (FromJSON s) => GFromJSON (C1 c (S1 (MetaSel 'Nothing su ss ds) (Rec0 s))) where- gFromJSON _ = M1 . M1 . K1 <$> fromJSON--instance GFromJSON U1 where- gFromJSON opts = U1 <$ parseNull+ objName = T.pack (fromJSONBaseName opts) <> "." <> conName+ conName = T.pack $ symbolVal (Proxy :: Proxy n) -instance {-# OVERLAPS #-} (Constructor t) => GFromJSON (C1 t U1) where- gFromJSON opts = M1 U1 <$ parseTextConstant conn+-- | If we have a constructor with arguments, and those arguments+-- do have selectors (IE, this is a record), then we should parse as a record.+instance {-# OVERLAPS #-} forall c i n s. (GFromJSONObject i, KnownSymbol n) => GFromJSON (C1 (MetaCons n s 'True) i) where+ gFromJSON opts = M1 <$> nameParser (T.pack name) (parseObject $ gFromJSONObject opts) where- conn = T.pack $ conName c- c :: C1 t U1 f- c = undefined+ name = fromJSONBaseName opts <> "." <> symbolVal (Proxy @n) -instance {-# OVERLAPS #-} (Constructor t) => GFromJSON (PartOfSum (C1 t U1)) where- gFromJSON opts = PartOfSum (M1 U1) <$ parseTextConstant enumValue+-- | Special-case: a one-argument constructor with no field selector gets its own parser, skipping the tuple entirely.+instance {-# OVERLAPS #-} (FromJSON inner, KnownSymbol n) => GFromJSON (C1 (MetaCons n s 'False) (S1 (MetaSel Nothing ss su dl) (Rec0 inner))) where+ gFromJSON opts =+ M1 . M1 . K1+ <$> fromJSON where- enumValue = T.pack $ convertEnum opts $ conName (undefined :: C1 t U1 f)+ connName = T.pack $ symbolVal $ Proxy @n -instance {-# OVERLAPPING #-} (GFromJSON (C1 t f), Constructor t) => GFromJSON (PartOfSum (C1 t f)) where- gFromJSON opts = PartOfSum <$> encoded+-- | When rendering a sum type, if we have a more complex value (IE, maybe+-- this is a constructor that takes arguments), we want to use whatever+-- sum encoding was provided in the options.+instance {-# OVERLAPPABLE #-} (GFromJSON (C1 t f), Constructor t) => GFromJSON (PartOfSum (C1 t f)) where+ gFromJSON opts = MkPartOfSum <$> encoded where encoded = case fromJSONEncodeSums opts of TagVal -> tagged TagInField -> field- tagged = parseObject (objName name) $- parseFieldWith "tag" (parseTextConstant name)- *> parseFieldWith "val" (gFromJSON opts)- field = parseObject (objName name) $- parseFieldWith name (gFromJSON opts)+ tagged =+ parseObject $+ parseFieldWith "tag" (parseTextConstant name)+ *> parseFieldWith "val" (gFromJSON opts)+ field =+ parseObject $+ parseFieldWith name (gFromJSON opts) name = T.pack $ conName (undefined :: C1 t f a)- objName a = T.pack (fromJSONBaseName opts <> ".") <> a <> ".Input"+ objName = T.pack (fromJSONBaseName opts <> ".") <> name +instance {-# OVERLAPPABLE #-} (KnownSymbol connName) => GFromJSON (C1 (MetaCons connName dontCare 'False) U1) where+ gFromJSON _ = M1 U1 <$ parseTextConstant constName+ where+ constName = T.pack (symbolVal $ Proxy @connName)++instance {-# OVERLAPS #-} (KnownSymbol connName) => GFromJSON (PartOfSum (C1 (MetaCons connName dontCare 'False) U1)) where+ gFromJSON opts = MkPartOfSum <$> gFromJSON opts++-- | If we can parse both sides of a sum-type, we can parse the entire sum type. instance {-# OVERLAPS #-} (GFromJSON (PartOfSum l), GFromJSON (PartOfSum r)) => GFromJSON (l :+: r) where gFromJSON opts = (L1 . getPartOfSum <$> gFromJSON opts) <> (R1 . getPartOfSum <$> gFromJSON opts) -instance (GFromJSON (PartOfSum l), GFromJSON (PartOfSum r)) => GFromJSON (PartOfSum (l :+: r)) where- gFromJSON opts = PartOfSum <$> gFromJSON opts--instance {-# OVERLAPPING #-} (Constructor t, Constructor t') =>- GFromJSON (C1 t U1 :+: C1 t' U1) where- gFromJSON ops = (L1 <$> gFromJSON ops) <> (R1 <$> gFromJSON ops)+instance {-# OVERLAPS #-} (GFromJSON (PartOfSum l), GFromJSON (PartOfSum r)) => GFromJSON (PartOfSum (l :+: r)) where+ gFromJSON opts =+ MkPartOfSum+ <$> (L1 . getPartOfSum <$> gFromJSON opts) <> (R1 . getPartOfSum <$> gFromJSON opts) +-- | Class that helps us parse JSON objects. class GFromJSONObject v where gFromJSONObject :: (JSONObjectParser f) => FromJSONOptions -> f (v a) instance GFromJSONObject U1 where gFromJSONObject _ = pure U1 -instance (FromJSON c, Selector t) => GFromJSONObject (S1 t (K1 v c)) where- gFromJSONObject o- = M1 . K1 <$> parseField (T.pack $ selName v)- where- v :: M1 S t f a- v = undefined+instance {-# OVERLAPPABLE #-} (FromJSON c, Selector t) => GFromJSONObject (S1 t (K1 v c)) where+ gFromJSONObject o =+ M1 . K1 <$> parseField (T.pack $ selName v)+ where+ v :: M1 S t f a+ v = undefined +instance {-# OVERLAPS #-} (FromJSON c, Selector t) => GFromJSONObject (S1 t (K1 v (Maybe c))) where+ gFromJSONObject o =+ M1 . K1 <$> parse+ where+ parse+ | fromJSONOmitNothingFields o = parseFieldWithDefault field ((Just <$> fromJSON) <> (parseNull $> Nothing)) Nothing+ | otherwise = parseField field+ field = T.pack $ selName v+ v :: M1 S t f a+ v = undefined+ instance (GFromJSONObject lhs, GFromJSONObject rhs) => GFromJSONObject (lhs :*: rhs) where gFromJSONObject o = (:*:) <$> gFromJSONObject o <*> gFromJSONObject o++class GFromJSONTuple v where+ gFromJSONTuple :: (JSONTupleParser f) => FromJSONOptions -> f (v a)++instance (GFromJSONTuple lhs, GFromJSONTuple rhs) => GFromJSONTuple (lhs :*: rhs) where+ gFromJSONTuple o = (:*:) <$> gFromJSONTuple o <*> gFromJSONTuple o++instance (GFromJSON f) => GFromJSONTuple (S1 (MetaSel Nothing su ss ds) f) where+ gFromJSONTuple o = M1 <$> consumeItemWith (gFromJSON o)
+ lib/Jordan/FromJSON/Internal/Attoparsec.hs view
@@ -0,0 +1,431 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}++module Jordan.FromJSON.Internal.Attoparsec where++import Control.Applicative (Alternative (..))+import Control.Monad (void, when)+import Data.Attoparsec.ByteString ((<?>))+import qualified Data.Attoparsec.ByteString as A+import qualified Data.Attoparsec.ByteString as AP+import qualified Data.Attoparsec.ByteString.Char8 as CH+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Unsafe as B+import Data.Char (chr, digitToInt, isControl, isHexDigit, ord)+import Data.Functor (void, ($>))+import Data.Monoid (Alt (..))+import Data.Scientific (Scientific)+import qualified Data.Scientific as Sci+import qualified Data.Scientific as Scientific+import qualified Data.Text as Text+import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Numeric (showHex)++skipSpace :: AP.Parser ()+skipSpace = AP.skipWhile isSpace <?> "skipped space"+ where+ isSpace = \case+ 32 -> True+ 10 -> True+ 13 -> True+ 9 -> True+ _ -> False++lexeme :: AP.Parser a -> AP.Parser a+lexeme a = a <* skipSpace++label :: String -> AP.Parser a -> AP.Parser a+label l p = p <?> l++parseAnyField :: AP.Parser ()+parseAnyField =+ {-# SCC ignoredObjectField #-}+ label "ignored object field" $+ void $ do+ lexeme parseJunkText+ labelSep+ anyDatum++objectEndWithJunk :: AP.Parser ()+objectEndWithJunk = endObject <|> junkFieldAndEnd+ where+ junkFieldAndEnd = void $ do+ comma+ label "ignored extra field at object end" parseAnyField+ objectEndWithJunk++comma :: AP.Parser ()+comma = label "comma character" $ void $ lexeme (AP.string ",")++quotation :: AP.Parser ()+quotation = label "quotation mark" $ void $ AP.word8 34++parseJSONText :: AP.Parser Text.Text+parseJSONText = label "JSON text" $ do+ quotation+ innerText++-- | A parser for a JSON text value that skips its input.+-- Avoids doing UTF-8 Decoding.+parseJunkText :: AP.Parser ()+parseJunkText = label "Ignored JSON Text Literal" $ do+ quotation+ junkInnerText+{-# INLINE parseJunkText #-}++-- | Parses the bit of a JSON string after the quotation.+innerText :: AP.Parser Text.Text+innerText = do+ chunk <- label "Skipped text body" $+ AP.takeWhile $ \char -> char /= 92 && char /= 34+ l <- AP.peekWord8+ case l of+ Nothing -> fail "string without end"+ Just 34 -> do+ AP.anyWord8+ pure $ decodeUtf8 chunk+ Just 92 -> do+ AP.anyWord8+ r <- label "escape value" parseEscape+ rest <- innerText+ pure $ decodeUtf8 chunk <> r <> rest+ Just _ -> fail "Impossibe: Parsed until we parsed a '\\' or a '\"', yet next char was neither"++junkInnerText :: AP.Parser ()+junkInnerText =+ {-# SCC ignoredTextBetweenQuotes #-}+ do+ AP.skipWhile $ \char -> char /= 92 && char /= 34+ !l <- AP.peekWord8+ case l of+ Nothing -> fail "string without end"+ Just 34 -> AP.anyWord8 $> ()+ Just 93 -> do+ AP.anyWord8+ parseEscape+ -- Yes we could save a miniscule amount of time by replacing this with an "ignoring" version.+ -- However, laziness means that we probably don't save *that* much.+ junkInnerText+ Just _ -> fail "Impossible: Skipped until we parsed a '\\' or a '\"', yet next char was neither"+{-# INLINE junkInnerText #-}++parseEscape :: AP.Parser Text.Text+parseEscape =+ quote+ <|> backslash+ <|> solidus+ <|> backspace+ <|> formfeed+ <|> linefeed+ <|> carriage+ <|> tab+ <|> escaped+ where+ backslash = AP.string "\\" $> "\\" <?> "Backslash escape"+ quote = AP.string "\"" $> "\"" <?> "Quote escape"+ solidus = AP.string "/" $> "/" <?> "Solidus escape"+ backspace = AP.string "b" $> "\b" <?> "Backspace escape"+ formfeed = AP.string "f" $> "\f" <?> "Formfeed escape"+ linefeed = AP.string "n" $> "\n" <?> "Linefeed escape"+ carriage = AP.string "r" $> "\r" <?> "Carriage escape"+ tab = AP.string "t" $> "\t" <?> "Tab escape"+ escaped = label "UTF Code Escape" $ do+ AP.string "u"+ a <- parseHexDigit+ b <- parseHexDigit+ c <- parseHexDigit+ d <- parseHexDigit+ let s = (((a * 16) + b) * 16 + c) * 16 + d+ pure $ Text.pack [chr s]++parseHexDigit :: AP.Parser Int+parseHexDigit = label "hex digit" (digitToInt <$> CH.satisfy isHexDigit)++parseCharInText :: Char -> AP.Parser ()+parseCharInText a = parseLit a <|> escaped a+ where+ parseLit :: Char -> AP.Parser ()+ parseLit = \case+ '\\' -> void $ AP.string "\\\\"+ '"' -> void $ AP.string "\\\""+ '/' -> void $ AP.string "/" <|> AP.string "\\/"+ '\b' -> void $ AP.string "\\b"+ '\f' -> void $ AP.string "\\f"+ '\n' -> void $ AP.string "\\n"+ '\r' -> void $ AP.string "\\r"+ '\t' -> void $ AP.string "\\t"+ a -> if isControl a then empty else void $ AP.string $ encodeUtf8 $ Text.singleton a+ escaped :: Char -> AP.Parser ()+ escaped a = void $ AP.string $ encodeUtf8 $ Text.pack $ withEscaped $ (showHex $ ord a) []+ withEscaped :: String -> String+ withEscaped a@[_] = "\\u000" <> a+ withEscaped a@[_, _] = "\\u00" <> a+ withEscaped a@[_, _, _] = "\\u0" <> a+ withEscaped r = "\\u" <> r++mustBeEscaped :: Char -> Bool+mustBeEscaped = \case+ '\\' -> True+ '"' -> True+ '/' -> True+ '\b' -> True+ '\f' -> True+ '\n' -> True+ '\r' -> True+ '\t' -> True+ _ -> False++canParseDirectly :: Text.Text -> Bool+canParseDirectly t = not $ Text.foldr (\c v -> v || mustBeEscaped c) False t++parseTextBody :: Text.Text -> AP.Parser ()+parseTextBody text+ | canParseDirectly text = void (A.string (encodeUtf8 text)) <|> parseViaChars text+ | otherwise = parseViaChars text++parseViaChars = Text.foldr (\c a -> parseCharInText c *> a) (pure ())++objectKey :: Text.Text -> AP.Parser ()+objectKey k = lexeme $ do+ quotation+ {-# SCC "knownObjectKeyBetweenQuotes" #-} parseTextBody k+ quotation+ pure ()++startObject :: AP.Parser ()+startObject =+ label "object starting brace ('{')" $+ lexeme $+ void $+ AP.word8 123++endObject :: AP.Parser ()+endObject =+ label "object ending brace ('}')" $+ lexeme $+ void $+ AP.word8 125++inObjectBraces :: AP.Parser a -> AP.Parser a+inObjectBraces interior = startObject *> interior <* endObject++startArray :: AP.Parser ()+startArray =+ label "array starting brace ('[')" $+ lexeme $+ void $+ AP.word8 91++endArray :: AP.Parser ()+endArray =+ label "array ending brace (']')" $+ lexeme $+ void $+ AP.word8 93++labelSep :: AP.Parser ()+labelSep = label "key-value separator (':')" $ void $ lexeme $ AP.string ":"++anyDatum :: AP.Parser ()+anyDatum =+ lexeme $+ {-# SCC "ignoredJSONValue" #-}+ do+ t <- AP.peekWord8+ case t of+ Just 102 -> void $ AP.string "false"+ Just 110 -> void $ AP.string "null"+ Just 116 -> void $ AP.string "true"+ Just 123 -> anyObject+ Just 34 -> parseJunkText+ Just 43 -> parseJunkNumber+ Just 45 -> parseJunkNumber+ Just 48 -> parseJunkNumber+ Just 49 -> parseJunkNumber+ Just 50 -> parseJunkNumber+ Just 51 -> parseJunkNumber+ Just 52 -> parseJunkNumber+ Just 53 -> parseJunkNumber+ Just 54 -> parseJunkNumber+ Just 55 -> parseJunkNumber+ Just 57 -> parseJunkNumber+ Just 59 -> parseJunkNumber+ Just 91 -> anyArray+ Just _ -> fail "not a valid starter of any JSON value"+ Nothing -> fail "empty input"+{-# INLINE anyDatum #-}++anyArray :: AP.Parser ()+anyArray = label "ignored array" $+ void $ do+ startArray+ endArray <|> junkItems+ where+ junkItems = do+ anyDatum+ endArray <|> (comma *> junkItems)++parseJunkDecimalZero :: AP.Parser ()+parseJunkDecimalZero = do+ let zero = 48+ digits <- A.takeWhile1 CH.isDigit_w8+ when (B.length digits > 1 && B.unsafeHead digits == zero) $+ fail "leading zero"++parseJunkExponent :: AP.Parser ()+parseJunkExponent = label "junk exponent" $ do+ A.satisfy (\ex -> ex == 101 || ex == 69)+ A.skipWhile (\ch -> ch == 45 || ch == 43)+ parseJunkDecimalZero++parseJunkNumber :: AP.Parser ()+parseJunkNumber = do+ A.skipWhile (\ch -> ch == 45 || ch == 43)+ parseJunkDecimalZero+ -- skip decimal+ dot <- A.peekWord8+ case dot of+ Just 46 -> void $ A.anyWord8 *> A.takeWhile1 CH.isDigit_w8+ _ -> pure ()+ parseJunkExponent <|> pure ()++------ Scientific parser, copy/pasted from Aeson. ----++-- (This parser was in turn copy-pasted itself from various soruces so this is kohser)++-- A strict pair+data SP = SP !Integer {-# UNPACK #-} !Int++decimal0 :: AP.Parser Integer+decimal0 = do+ let zero = 48+ digits <- A.takeWhile1 CH.isDigit_w8+ if B.length digits > 1 && B.unsafeHead digits == zero+ then fail "leading zero"+ else return (bsToInteger digits)++-- | Parse a JSON number.+--+-- This function is wholesale copy/pasted from Aeson.+-- Thanks to them.+scientific :: AP.Parser Scientific+scientific = do+ let minus = 45+ plus = 43+ sign <- A.peekWord8'+ let !positive = sign == plus || sign /= minus+ when (sign == plus || sign == minus) $+ void A.anyWord8++ n <- decimal0++ let f fracDigits =+ SP+ (B.foldl' step n fracDigits)+ (negate $ B.length fracDigits)+ step a w = a * 10 + fromIntegral (w - 48)++ dotty <- A.peekWord8+ -- '.' -> ascii 46+ SP c e <- case dotty of+ Just 46 -> A.anyWord8 *> (f <$> A.takeWhile1 CH.isDigit_w8)+ _ -> pure (SP n 0)++ let !signedCoeff+ | positive = c+ | otherwise = - c++ let littleE = 101+ bigE = 69+ ( A.satisfy (\ex -> ex == littleE || ex == bigE)+ *> fmap (Scientific.scientific signedCoeff . (e +)) (CH.signed CH.decimal)+ )+ <|> return (Scientific.scientific signedCoeff e)+{-# INLINE scientific #-}++bsToInteger :: B.ByteString -> Integer+bsToInteger bs+ | l > 40 = valInteger 10 l [fromIntegral (w - 48) | w <- B.unpack bs]+ | otherwise = bsToIntegerSimple bs+ where+ l = B.length bs++bsToIntegerSimple :: B.ByteString -> Integer+bsToIntegerSimple = B.foldl' step 0+ where+ step a b = a * 10 + fromIntegral (b - 48) -- 48 = '0'++-- A sub-quadratic algorithm for Integer. Pairs of adjacent radix b+-- digits are combined into a single radix b^2 digit. This process is+-- repeated until we are left with a single digit. This algorithm+-- performs well only on large inputs, so we use the simple algorithm+-- for smaller inputs.+valInteger :: Integer -> Int -> [Integer] -> Integer+valInteger = go+ where+ go :: Integer -> Int -> [Integer] -> Integer+ go _ _ [] = 0+ go _ _ [d] = d+ go b l ds+ | l > 40 = b' `seq` go b' l' (combine b ds')+ | otherwise = valSimple b ds+ where+ -- ensure that we have an even number of digits+ -- before we call combine:+ ds' = if even l then ds else 0 : ds+ b' = b * b+ l' = (l + 1) `quot` 2++ combine b (d1 : d2 : ds) = d `seq` (d : combine b ds)+ where+ d = d1 * b + d2+ combine _ [] = []+ combine _ [_] = errorWithoutStackTrace "this should not happen"++-- The following algorithm is only linear for types whose Num operations+-- are in constant time.+valSimple :: Integer -> [Integer] -> Integer+valSimple base = go 0+ where+ go r [] = r+ go r (d : ds) = r' `seq` go r' ds+ where+ r' = r * base + fromIntegral d++number :: AP.Parser Scientific+number = scientific+{-# INLINE number #-}++anyObject :: AP.Parser ()+anyObject =+ {-# SCC ignoredJSONObject #-}+ label "ignored object" $+ void $ do+ startObject+ endObject <|> junkField+ where+ junkField = do+ parseAnyField+ endObject <|> (comma *> junkField)++parseObjectField ::+ Text.Text ->+ AP.Parser a ->+ AP.Parser a+parseObjectField t f = do+ objectKey t+ labelSep+ lexeme f++parseDictField ::+ AP.Parser a ->+ AP.Parser (Text.Text, a)+parseDictField p = do+ key <- lexeme parseJSONText+ labelSep+ val <- p+ pure (key, val)
+ lib/Jordan/FromJSON/Internal/Permutation.hs view
@@ -0,0 +1,153 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RoleAnnotations #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}++-- | Module containing internal helpers for our parsers.+module Jordan.FromJSON.Internal.Permutation where++import Control.Applicative (Alternative (..))+import Control.Monad (void, when)+import Data.Bifunctor+import Data.Foldable (asum)+import Data.Functor.Compose+import qualified Data.Map.Lazy as Map+import Data.Maybe (fromMaybe, isJust)+import Debug.Trace++data FailingParser parser+ = FailingParser (forall a. parser a)+ | NoFailingParser++instance (Applicative parser) => Semigroup (FailingParser parser) where+ (FailingParser a) <> (FailingParser b) = FailingParser (a *> b)+ (FailingParser a) <> NoFailingParser = FailingParser a+ NoFailingParser <> (FailingParser a) = FailingParser a+ NoFailingParser <> NoFailingParser = NoFailingParser++eliminateFailing :: (Alternative parser) => FailingParser parser -> parser a+eliminateFailing (FailingParser f) = f+eliminateFailing NoFailingParser = empty++type role Permutation nominal representational++-- | A parser for permutations.+--+-- Based on the paper Parsing Permutation Phrases by+-- Arthur Baars, Andres Loh, and S. Doaitse Swierstra.+--+-- The source code for 'Control.Applicative.Permutations' really helped+-- in writing this, although this type is structured differently (and closer to the actual paper).+-- Thank you very much to Alex Washburn!+data Permutation parser a+ = Permutation !(Maybe a) !(FailingParser parser) [Branch parser a]++type role Branch nominal representational++-- | A branch of a permutation.+-- Permutation parsers work by building up the entire tree of+-- possible parsers, which is efficient in Haskell due to laziness.+data Branch parser a+ = forall arg. Branch (Permutation parser (arg -> a)) (parser arg)++instance (Functor m) => Functor (Branch m) where+ fmap f (Branch perm p) = Branch (fmap (f .) perm) p++instance (Functor m) => Functor (Permutation m) where+ fmap f (Permutation def failing branches) =+ Permutation (f <$> def) failing (fmap f <$> branches)++instance (Alternative m) => Applicative (Branch m) where+ pure a = Branch (pure $ const a) (pure ())+ (Branch permuteF argF) <*> (Branch permuteA argA) =+ Branch (args <$> permuteF <*> permuteA) arguments+ where+ arguments = ((,) <$> argA <*> argF) <|> (flip (,) <$> argF <*> argA)+ args :: (arg1 -> a -> b) -> (arg2 -> a) -> (arg2, arg1) -> b+ args f a (aa, fa) = f fa (a aa)++instance (Alternative m) => Applicative (Permutation m) where+ pure val = Permutation (Just val) NoFailingParser empty++ t1@(Permutation defF failingF choiceF) <*> t2@(Permutation defA failingA choiceA) =+ Permutation (defF <*> defA) NoFailingParser (map ins2 choiceF ++ map ins1 choiceA)+ where+ ins1 (Branch perm p) = Branch ((.) <$> t1 <*> perm) p+ ins2 (Branch perm p) = Branch (flip <$> perm <*> t2) p++-- | Wrap up a permutation parser with two effects:+--+-- It will first interleave an infinite number of some effects, which represent parsing "junk" or unwanted fields.+-- At every stage of the permutation, we will first try to run the effect we want, and if it fails+-- we will try to run the "junk" effect instead, then try again.+--+-- We attempt to *intersperse* the second effect afterwards.+-- It adds a new effect between every effect.+-- This is used in parsing JSON to add commas.+wrapEffect ::+ forall m a b.+ (Alternative m) =>+ -- | Consume a single, \"junk\" field.+ -- Used to ignore JSON keys that we do not care about.+ m b ->+ -- | Consume a \"separator\" between items in the permutation.+ -- This consumption is not done at the front of the permutation+ -- or after the end of it.+ -- This is used to parse commas between JSON fields.+ m b ->+ -- | The permutation parser to run.+ Permutation m a ->+ -- | The final parser.+ m a+wrapEffect takeSingle effAfter (Permutation def failing choices) = consumeMany+ where+ consumeMany =+ foldr ((<|>) . pars) empty choices+ -- Base case above: one of the choices of the permutation matched+ <|> (takeSingle *> effAfter *> consumeMany)+ <|> maybe empty pure def++ -- Interleaving case: none of the choices of the permutation matched,+ -- so run a "junk" effect, the separator, and try again.+ -- Due to the recursion here we will do this infinitely until we either cannot+ -- run the junk effect, *or* we have a field that matches one of the choices of the permutation.+ runWithEffect :: Permutation m whatever -> m whatever+ runWithEffect (Permutation def failing choices) = (effAfter *> consumeRec) <|> maybe (eliminateFailing failing) pure def+ where+ consumeRec =+ foldr ((<|>) . pars) empty choices+ -- Run one of the effects from the permutation+ <|> (takeSingle *> effAfter *> consumeRec)+ -- Interleave a potentially infinite number of junk effects, with the separator effect between them.+ pars :: Branch m whatever -> m whatever+ pars (Branch perm arg) = do+ a <- arg+ rest <- runWithEffect perm+ pure $ rest a++asParser :: (Alternative f) => Permutation f a -> f a+asParser (Permutation def failing choices) = asum (pars <$> choices) <|> maybe empty pure def <|> eliminateFailing failing+ where+ pars :: (Alternative f) => Branch f a -> f a+ pars (Branch perm arg) = do+ a <- arg+ rest <- asParser perm+ pure $ rest a++asPermutation :: (Alternative f) => f a -> Permutation f a+asPermutation p = Permutation Nothing NoFailingParser $ pure $ Branch (pure id) p++asPermutationWithDefault :: (Alternative f) => f a -> a -> Permutation f a+asPermutationWithDefault per def = Permutation (Just def) NoFailingParser $ pure $ Branch (pure id) per++asPermutationWithFailing :: (Alternative f) => f a -> (forall b. f b) -> Permutation f a+asPermutationWithFailing parse fail = Permutation Nothing (FailingParser fail) $ pure $ Branch (pure id) parse++asPermutationWithDefaultFailing :: (Alternative f) => f a -> (forall b. f b) -> a -> Permutation f a+asPermutationWithDefaultFailing parse fail def = Permutation (Just def) (FailingParser fail) $ pure $ Branch (pure id) parse
+ lib/Jordan/FromJSON/Internal/UnboxedParser.hs view
@@ -0,0 +1,546 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UnboxedSums #-}+{-# LANGUAGE UnboxedTuples #-}+{-# LANGUAGE UnliftedNewtypes #-}+{-# LANGUAGE NoMonomorphismRestriction #-}++-- | A parser module using unboxed types for speed.+--+-- This is done because parsing to a JSON error report needs custom handling.+module Jordan.FromJSON.Internal.UnboxedParser where++import Control.Applicative+import Control.Monad (when)+import Data.Bifunctor+import qualified Data.ByteString as BS+import Data.ByteString.Internal+import Data.Functor+import Data.Monoid (Alt (..))+import Data.Word+import Debug.Trace (trace)+import GHC.Exts+import GHC.ForeignPtr+import GHC.Prim+import GHC.Types+import GHC.Word+import qualified Jordan.Types.Internal.AccumE as AE+import Jordan.Types.JSONError (JSONArrayError, JSONError, JSONObjectError)+import System.IO.Unsafe++#if __GLASGOW_HASKELL__ > 900+-- | Type of Word8 in GHC prim land. On GHC > 9.0, this is its own type.+type WordPrim = Word8#+#elif __GLASGOW_HASKELL__ > 800+-- | Type of word8 in GHC prim land.+-- On the GHC 8 series, this is 'Word#'.+type WordPrim = Word#+#endif++-- | Newtype wrapper around the state of an input.+--+-- This is just the offset into the buffer.+newtype InputState = InputState# {getInputOffset :: Int#}++-- | Pattern synonym so we can use our above unlifted newtype like a record, if we do desire.+pattern InputState :: Int# -> InputState+pattern InputState {offset} =+ InputState# offset++{-# COMPLETE InputState #-}++maxOffset :: InputState -> InputState -> InputState+maxOffset (InputState# lhs) (InputState# rhs) =+ InputState#+ (if isTrue# (lhs ># rhs) then lhs else rhs)++-- | Environment of a parser.+--+-- This is basically unpacked parts of a ByteString.+newtype InputRead = InputRead# {getInputRead :: (# ForeignPtrContents, Addr#, Int# #)}++pattern InputRead :: ForeignPtrContents -> Addr# -> Int# -> InputRead+pattern InputRead {foreignPtr, addr, endOffset} = InputRead# (# foreignPtr, addr, endOffset #)++{-# COMPLETE InputRead #-}++-- | Unboxed type similar to 'Jordan.Types.Internal.AccumE'.+newtype AccumE err a = AccumE {getAccumE :: (# err| a #)}++pattern AccumER :: a -> AccumE err a+pattern AccumER a = AccumE (# | a #)++pattern AccumEL :: err -> AccumE err a+pattern AccumEL err = AccumE (# err | #)++{-# COMPLETE AccumEL, AccumER #-}++newtype ParseResult# err res = ParseResult# {getParseResult# :: (# (# InputState, AccumE err res #)| (# #) #)}++pattern JustParseResult :: InputState -> AccumE err res -> ParseResult# err res+pattern JustParseResult {inputState, res} = ParseResult# (# (# inputState, res #) | #)++pattern NoParseResult :: ParseResult# err res+pattern NoParseResult = ParseResult# (# | (##) #)++bimapAcc :: (err -> err') -> (a -> a') -> AccumE err a -> AccumE err' a'+bimapAcc first _ (AccumEL a) = AccumEL (first a)+bimapAcc _ second (AccumER a) = AccumER (second a)++eitherAcc :: Either err a -> AccumE err a+eitherAcc (Left e) = AccumEL e+eitherAcc (Right a) = AccumER a++appAcc ::+ Semigroup err =>+ AccumE err (a1 -> a2) ->+ AccumE err a1 ->+ AccumE err a2+appAcc (AccumER f) (AccumER a) = AccumER (f a)+appAcc (AccumEL lhs) (AccumEL rhs) = AccumEL (lhs <> rhs)+appAcc (AccumEL lhs) _ = AccumEL lhs+appAcc _ (AccumEL rhs) = AccumEL rhs++accSet :: a1 -> AccumE err a2 -> AccumE err a1+accSet a (AccumER _) = AccumER a+accSet _ (AccumEL err) = AccumEL err++{-# COMPLETE JustParseResult, NoParseResult #-}++-- | We need a parser with *error recovery*.+-- So the basic idea is that we separate errors reported during parsing from errors that make parsing stop.+-- IE, if we expect a JSON null but we get a JSON string, and the string is well-formed, we can keep parsing, but we+-- will *report* an error.+newtype Parser# s err res = Parser# {runParser :: InputRead -> InputState -> State# s -> (# State# s, ParseResult# err res #)}++bimapParser :: (err -> err') -> (a -> a') -> Parser# s err a -> Parser# s err' a'+bimapParser l r (Parser# cb) = Parser# $ \env input s ->+ case cb env input s of+ (# s', a #) ->+ (#+ s',+ case a of+ NoParseResult -> NoParseResult+ JustParseResult is e -> JustParseResult is (bimapAcc l r e)+ #)++fmapParser :: (a -> res) -> Parser# s err a -> Parser# s err res+fmapParser f (Parser# cb) = Parser# $ \env input s ->+ case cb env input s of+ (# s', a #) ->+ (#+ s,+ case a of+ NoParseResult -> NoParseResult+ JustParseResult is (AccumER !r) -> JustParseResult is (AccumER (f r))+ JustParseResult is (AccumEL !l) -> JustParseResult is (AccumEL l)+ #)+{-# INLINE fmapParser #-}++pureParser :: (Semigroup err) => a -> Parser# s err a+pureParser !a = Parser# $ \_ state s -> (# s, JustParseResult state (AccumER a) #)+{-# INLINE pureParser #-}++apParser :: (Semigroup err) => Parser# s err (a -> b) -> Parser# s err a -> Parser# s err b+apParser (Parser# fcb) (Parser# acb) = Parser# $ \env input s ->+ case fcb env input s of+ (# s', NoParseResult #) -> (# s', NoParseResult #)+ (# s', JustParseResult !input' !f #) ->+ case acb env input' s' of+ (# s'', a #) ->+ (#+ s'',+ case a of+ NoParseResult -> NoParseResult+ JustParseResult !input'' !a -> JustParseResult input'' (f `appAcc` a)+ #)+{-# SPECIALIZE apParser :: Parser# s JSONError (a -> b) -> Parser# s JSONError a -> Parser# s JSONError b #-}+{-# SPECIALIZE apParser :: Parser# s JSONObjectError (a -> b) -> Parser# s JSONObjectError a -> Parser# s JSONObjectError b #-}+{-# SPECIALIZE apParser :: Parser# s JSONArrayError (a -> b) -> Parser# s JSONArrayError a -> Parser# s JSONArrayError b #-}+{-# INLINE apParser #-}++-- | Alternative instance for a parser.+-- This has weird behavior in that, if we have two results with delayed errors, this will act as if it skipped the *largest* amount+-- of said errors.+altParser :: (Monoid err) => Parser# s err a -> Parser# s err a -> Parser# s err a+altParser (Parser# lhs) (Parser# rhs) = Parser# $ \env input s ->+ let (# s', !lhs' #) = lhs env input s+ in case lhs' of+ -- If the parser failed to parse, we also fail to parse+ NoParseResult -> rhs env input s'+ JustParseResult state (AccumER !res) -> (# s', JustParseResult state (AccumER res) #)+ JustParseResult state res@(AccumEL !err) ->+ case rhs env input s' of+ (# s'', NoParseResult #) -> (# s'', JustParseResult state res #)+ (# s'', JustParseResult state' (AccumER !res) #) -> (# s'', JustParseResult (maxOffset state state') (AccumER res) #)+ (# s'', JustParseResult state' (AccumEL !err') #) ->+ (# s'', JustParseResult (maxOffset state state') (AccumEL (err <> err')) #)+{-# SPECIALIZE altParser :: Parser# s JSONError a -> Parser# s JSONError a -> Parser# s JSONError a #-}+{-# SPECIALIZE altParser :: Parser# s JSONObjectError a -> Parser# s JSONObjectError a -> Parser# s JSONObjectError a #-}+{-# SPECIALIZE altParser :: Parser# s JSONArrayError a -> Parser# s JSONArrayError a -> Parser# s JSONArrayError a #-}+{-# INLINE altParser #-}++-- | Monadic bind for these parsers.+--+-- Note that this breaks the monad laws, as we do more error accumulation with (<*>) than we do ap.+-- Oh well.+bindParser :: Parser# s err a -> (a -> Parser# s err b) -> Parser# s err b+bindParser (Parser# arg) cont =+ Parser# $+ {-# SCC unboxedParserBindInner #-}+ \env state s ->+ let (# s', result #) = arg env state s+ in case result of+ NoParseResult -> (# s', NoParseResult #)+ JustParseResult state' (AccumEL err) -> (# s', JustParseResult state (AccumEL err) #)+ JustParseResult state' (AccumER r) -> runParser (cont r) env state' s'+{-# INLINE bindParser #-}++emptyParser :: Parser# s err a+emptyParser = Parser# $ \_ _ s -> (# s, NoParseResult #)++newtype Parser err res = Parser {getParser :: Parser# RealWorld err res}+ deriving (Semigroup, Monoid) via (Alt (Parser err) res)++instance Bifunctor Parser where+ bimap l r (Parser p) = Parser (bimapParser l r p)+ {-# INLINE bimap #-}++instance Functor (Parser err) where+ fmap f (Parser a) = Parser (fmapParser f a)+ {-# INLINE fmap #-}+ a <$ (Parser (Parser# cb)) = Parser $+ Parser# $ \env state token ->+ let (# s', res #) = cb env state token+ in case res of+ NoParseResult -> (# s', NoParseResult #)+ JustParseResult state r -> (# s', JustParseResult state (a `accSet` r) #)+ {-# INLINE (<$) #-}++instance (Semigroup err) => Applicative (Parser err) where+ pure = Parser . pureParser+ {-# INLINE pure #-}+ (Parser f) <*> (Parser a) = Parser (f `apParser` a)+ {-# INLINE (<*>) #-}+ {-# SPECIALIZE (<*>) :: Parser JSONError (a -> b) -> Parser JSONError a -> Parser JSONError b #-}+ {-# SPECIALIZE (<*>) :: Parser JSONObjectError (a -> b) -> Parser JSONObjectError a -> Parser JSONObjectError b #-}++instance (Monoid err) => Alternative (Parser err) where+ empty = Parser emptyParser+ {-# INLINE empty #-}+ (Parser l) <|> (Parser r) = Parser (l `altParser` r)+ {-# INLINE (<|>) #-}++instance (Semigroup err) => Monad (Parser err) where+ (Parser f) >>= cb = Parser (f `bindParser` (\res -> getParser (cb res)))+ {-# INLINE (>>=) #-}++parseBSIO :: Parser err res -> ByteString -> IO (Maybe (AE.AccumE err res))+parseBSIO (Parser (Parser# cb)) (PS (ForeignPtr addr contents) offset' len) = IO parse'+ where+ (I# endOffset) = offset' + len+ (I# startOffset) = offset'+ parse' s =+ let (# s', res #) = cb (InputRead contents addr endOffset) (InputState# startOffset) s+ in case res of+ NoParseResult -> (# s', Nothing #)+ JustParseResult _ (AccumEL err) -> (# s', Just (AE.AccumEL err) #)+ JustParseResult _ (AccumER res) -> (# s', Just (AE.AccumER res) #)++parseBS :: Parser err res -> ByteString -> Maybe (AE.AccumE err res)+parseBS parser bs = unsafeDupablePerformIO (parseBSIO parser bs)++currentOffset :: Parser err Int+currentOffset = Parser $+ Parser# $ \env i@(InputState# cs) s ->+ (# s, JustParseResult i (AccumER (I# cs)) #)+{-# INLINE currentOffset #-}++getEndOffset :: Parser err Int+getEndOffset = Parser $+ Parser# $ \env i s ->+ (# s, JustParseResult i (AccumER (I# (endOffset env))) #)+{-# INLINE getEndOffset #-}++parsedPtr :: Parser err (ForeignPtr Word8)+parsedPtr = Parser $+ Parser# $ \env i s -> (# s, JustParseResult i (AccumER $ ForeignPtr (addr env) (foreignPtr env)) #)++currentEnv :: Parser err (ForeignPtrContents, Ptr a, Int)+currentEnv = Parser $+ Parser# $ \env i s -> (# s, JustParseResult i (AccumER (foreignPtr env, Ptr (addr env), I# (endOffset env))) #)++failParse :: Parser err a+failParse = Parser $+ Parser# $ \env i s -> (# s, NoParseResult #)++maybeWord :: Parser err (Maybe Word8)+maybeWord = Parser $+ Parser# $ \env state@(InputState# input) s ->+ if isTrue# (input ==# endOffset env)+ then (# s, JustParseResult state (AccumER Nothing) #)+ else+ let (# s', word #) = readWord8OffAddr# (addr env) input s+ in (# s', JustParseResult (InputState# (input +# 1#)) (AccumER $ Just $ W8# word) #)++orFail :: Parser err (Maybe a) -> Parser err a+orFail (Parser (Parser# cb)) = Parser $+ Parser# $ \env s state ->+ let (# s', r #) = cb env s state+ in case r of+ NoParseResult -> (# s', NoParseResult #)+ JustParseResult state a ->+ case a of+ AccumER (Just a) -> (# s', JustParseResult state (AccumER a) #)+ _ -> (# s', NoParseResult #)++failWith :: err -> Parser err a+failWith err = Parser $+ Parser# $ \env s state -> (# state, JustParseResult s (AccumEL err) #)++asFailure :: Parser err err -> Parser err a+asFailure (Parser (Parser# cb)) = Parser $+ Parser# $ \env s state ->+ let (# s', r #) = cb env s state+ in case r of+ NoParseResult -> (# s', NoParseResult #)+ JustParseResult state a ->+ case a of+ AccumER a -> (# s', JustParseResult state (AccumEL a) #)+ AccumEL a -> (# s', JustParseResult state (AccumEL a) #)++-- | Lower a parsed error to a *parser error*.+lowerErr :: Parser err (Either err a) -> Parser err a+lowerErr (Parser (Parser# cb)) = Parser $+ Parser# $ \env s state ->+ let (# s', r #) = cb env s state+ in case r of+ NoParseResult -> (# s', NoParseResult #)+ JustParseResult state res ->+ case res of+ AccumER e -> (# s', JustParseResult state (eitherAcc e) #)+ AccumEL l -> (# s', JustParseResult state (AccumEL l) #)++-- | Do we have any further input?+hasFurther :: Parser err Bool+hasFurther = Parser $+ Parser# $ \env i@(InputState# input) s ->+ if isTrue# (input ==# endOffset env)+ then (# s, JustParseResult i (AccumER False) #)+ else (# s, JustParseResult i (AccumER True) #)++-- | Advance forward one word, fail if we can't+advanceWord :: Parser err ()+advanceWord = Parser $+ Parser# $ \env (InputState# input) s ->+ if isTrue# (input ==# endOffset env)+ then (# s, NoParseResult #)+ else (# s, JustParseResult (InputState# (input +# 1#)) (AccumER ()) #)++-- | Peek the next word, fail if there's nothing there+peekWord :: Parser err Word8+peekWord = Parser $+ Parser# $ \env i@(InputState# input) s ->+ if isTrue# (input ==# endOffset env)+ then (# s, NoParseResult #)+ else+ let (# s', word #) = readWord8OffAddr# (addr env) input s+ in (# s', JustParseResult i (AccumER $ W8# word) #)++-- | Peek a word, or nothing.+-- Never fails.+peekWordMaybe :: Parser err (Maybe Word8)+peekWordMaybe = Parser $+ Parser# $ \env i@(InputState# input) s ->+ if isTrue# (input ==# endOffset env)+ then (# s, JustParseResult i (AccumER Nothing) #)+ else+ let (# s', word #) = readWord8OffAddr# (addr env) input s+ in (# s', JustParseResult i (AccumER (Just (W8# word))) #)++word :: Parser err Word8+word = Parser $+ Parser# $ \env (InputState# input) s ->+ if isTrue# (input ==# endOffset env)+ then (# s, NoParseResult #)+ else+ let (# s', word #) = readWord8OffAddr# (addr env) input s+ in (# s', JustParseResult (InputState# (input +# 1#)) (AccumER $ W8# word) #)++specificWord w = orFail (cb <$> word)+ where+ cb w' =+ if w == w' then Just () else Nothing++-- | Skip over while the callback returns true.+--+-- Unlifted version, probably use skipWord8+skipWord8# :: (WordPrim -> Bool) -> Parser err ()+skipWord8# cb =+ Parser $+ Parser# (skipWord8CB# cb)+{-# INLINE skipWord8# #-}++-- | Skip over while the callback returns true+skipWord8 :: (Word8 -> Bool) -> Parser err ()+skipWord8 cb = skipWord8# (\byte -> cb (W8# byte {- HLINT ignore "Avoid lambda" -}))+{-# INLINE skipWord8 #-}++-- | Private: callback used for skipWord8+skipWord8CB# ::+ (WordPrim -> Bool) ->+ InputRead ->+ InputState ->+ State# RealWorld ->+ (# State# RealWorld, ParseResult# err () #)+skipWord8CB# cb env (InputState# input) s =+ let (# s', newOff #) = go input s+ in (# s', JustParseResult (InputState# newOff) (AccumER ()) #)+ where+ go :: Int# -> State# RealWorld -> (# State# RealWorld, Int# #)+ go inputOffset s =+ if isTrue# (inputOffset ==# endOffset env)+ then (# s, inputOffset #)+ else+ let (# s', word #) = readWord8OffAddr# (addr env) inputOffset s+ in if cb word then go (inputOffset +# 1#) s' else (# s', inputOffset #)++skipWhitespace :: Parser err ()+skipWhitespace = Parser $ Parser# skipWhitespaceCB+{-# INLINE skipWhitespace #-}++skipWhitespaceCB ::+ InputRead ->+ InputState ->+ State# RealWorld ->+ (# State# RealWorld, ParseResult# err () #)+skipWhitespaceCB env (InputState# input) s =+ let (# s', newOff #) = go input s+ in (# s', JustParseResult (InputState# newOff) (AccumER ()) #)+ where+ go :: Int# -> State# RealWorld -> (# State# RealWorld, Int# #)+ go inputOffset s+ | isTrue# (inputOffset ==# endOffset env) = (# s, inputOffset #)+ | otherwise =+ let (# s', word #) = readWord8OffAddr# (addr env) inputOffset s+ in case W8# word of+ 40 -> go (inputOffset +# 1#) s'+ 0x20 -> go (inputOffset +# 1#) s'+ 0x0A -> go (inputOffset +# 1#) s'+ 0x0D -> go (inputOffset +# 1#) s'+ 0x09 -> go (inputOffset +# 1#) s'+ _ -> (# s', inputOffset #)+ {-# INLINE go #-}+{-# INLINE skipWhitespaceCB #-}++signed :: (Monoid err, Num a) => Parser err a -> Parser err a+signed parser = withSign <|> parser+ where+ withSign = do+ r <- (specificWord 43 $> True) <|> (specificWord 45 $> False)+ if r+ then negate <$> parser+ else parser+{-# INLINE signed #-}++orNegative :: (Monoid err, Num a) => Parser err a -> Parser err a+orNegative parse =+ (specificWord 45 *> (negate <$> parse))+ <|> parse+{-# INLINE orNegative #-}++-- | Parse an integral number with possible leading zeros.+parseIntegral :: forall err i. (Monoid err, Integral i) => Parser err (Int, i)+parseIntegral = parseIntegralGo 0 0+{-# INLINE parseIntegral #-}++parseIntegralNoLeadingZero :: forall err i. (Monoid err, Integral i) => Parser err (Int, i)+parseIntegralNoLeadingZero = do+ w <- word+ if w >= 49 && w <= 57+ then parseIntegralGo 1 (fromIntegral $ w - 48)+ else failParse+{-# INLINE parseIntegralNoLeadingZero #-}++parseIntegralGo :: (Monoid err, Integral i) => Int -> i -> Parser err (Int, i)+parseIntegralGo digits acc = do+ r <- peekWordMaybe+ case r of+ Nothing -> pure (digits, acc)+ Just !w+ | w >= 48 && w <= 57 -> do+ let !accd = fromIntegral $ w - 48+ let !after = (acc * 10) + accd+ if after < 0 -- checks for overflow.+ then failParse+ else word *> parseIntegralGo (digits + 1) after+ | otherwise -> failParse+{-# SPECIALIZE parseIntegralGo :: (Monoid err) => Int -> Int -> Parser err (Int, Int) #-}+{-# SPECIALIZE parseIntegralGo :: (Monoid err) => Int -> Integer -> Parser err (Int, Integer) #-}++takeWord8Cont :: (Semigroup err) => (Word8 -> Bool) -> (BS.ByteString -> a) -> Parser err a+takeWord8Cont cb cont = do+ ptr <- parsedPtr+ offsetBefore <- currentOffset+ skipWord8 cb+ offsetAfter <- currentOffset+ pure $ cont (PS ptr offsetBefore (offsetAfter - offsetBefore))+{-# INLINE takeWord8Cont #-}++takeWord8 :: (Semigroup err) => (Word8 -> Bool) -> Parser err BS.ByteString+takeWord8 cb = takeWord8Cont cb id++takeWord81 :: (Semigroup err) => (Word8 -> Bool) -> Parser err BS.ByteString+takeWord81 cb = takeWord81Cont cb id++takeWord81Cont ::+ Semigroup err =>+ (Word8 -> Bool) ->+ (ByteString -> b) ->+ Parser err b+takeWord81Cont cb cont = do+ ptr <- parsedPtr+ offsetBefore <- currentOffset+ skipWord8 cb+ offsetAfter <- currentOffset+ let len = offsetAfter - offsetBefore+ when (len < 1) failParse+ pure $ cont (PS ptr offsetBefore len)++peekRest :: (Semigroup err) => Parser err BS.ByteString+peekRest = do+ ptr <- parsedPtr+ offset <- currentOffset+ es <- getEndOffset+ pure $ PS ptr offset (es - offset)++chunkOfLength :: Int -> Parser err BS.ByteString+chunkOfLength len@(I# len') = Parser $+ Parser# $ \env is@(InputState# off) s ->+ if+ | len < 0 -> (# s, NoParseResult #)+ | isTrue# (off +# len' ># endOffset env) -> (# s, NoParseResult #)+ | otherwise ->+ (#+ s,+ JustParseResult+ (InputState# (off +# len'))+ (AccumER $ PS (ForeignPtr (addr env) (foreignPtr env)) (I# off) len)+ #)++parseChunk :: ByteString -> Parser err ()+parseChunk chunk = orFail $ isChunkMaybe <$> chunkOfLength (BS.length chunk)+ where+ isChunkMaybe c = if chunk == c then Just () else Nothing++testParser :: Parser () (Word8, ByteString)+testParser = ((,) <$> word <*> takeWord8 (\c -> c >= 38 && c <= 57)) <|> ((,) <$> word <*> pure mempty)
+ lib/Jordan/FromJSON/Internal/UnboxedReporting.hs view
@@ -0,0 +1,351 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE NoMonomorphismRestriction #-}++module Jordan.FromJSON.Internal.UnboxedReporting where++import Control.Applicative (Alternative (empty, (<|>)))+import Control.Applicative.Combinators (sepBy)+import Control.Monad (when)+import Data.Bifunctor+import qualified Data.ByteString as BS+import Data.ByteString.Unsafe as BS+import Data.Char (chr, isControl, ord)+import Data.Functor (void, ($>))+import qualified Data.Map.Strict as Map+import Data.Maybe (isJust)+import Data.Monoid (Alt (..))+import Data.Scientific (Scientific)+import qualified Data.Scientific as Scientific+import qualified Data.Text as T+import Data.Text.Encoding (decodeUtf8', encodeUtf8)+import Data.Word (Word8)+import Debug.Trace (traceM)+import Jordan.FromJSON.Class+import Jordan.FromJSON.Internal.Attoparsec (bsToInteger)+import Jordan.FromJSON.Internal.Permutation+import Jordan.FromJSON.Internal.UnboxedParser as UP hiding (AccumE (..), AccumEL, AccumER)+import Jordan.Types.Internal.AccumE (AccumE (AccumE))+import Jordan.Types.JSONError+ ( JSONArrayError (..),+ JSONError+ ( ErrorBadArray,+ ErrorBadObject,+ ErrorBadTextConstant,+ ErrorBadType,+ ErrorInvalidJSON,+ ErrorMesage,+ ErrorNoValue+ ),+ JSONObjectError (..),+ )+import Jordan.Types.JSONType (JSONType (..))+import Numeric (showHex)++skipWithFailure :: JSONError -> Parser JSONError a+skipWithFailure err =+ UP.asFailure $+ skipAnything $> err++lexeme :: Semigroup err => Parser err a -> Parser err a+lexeme p = p <* UP.skipWhitespace+{-# INLINE lexeme #-}++jsonTypeFromWord :: Word8 -> Maybe JSONType+jsonTypeFromWord jt+ | jt == 34 = pure JSONTypeText+ | jt == 116 || jt == 102 = pure JSONTypeBool+ | jt == 110 = pure JSONTypeNull+ | jt >= 48 && jt <= 57 = pure JSONTypeNumber+ | jt == 45 = pure JSONTypeNumber+ | jt == 91 = pure JSONTypeArray+ | jt == 123 = pure JSONTypeObject+ | otherwise = Nothing+{-# INLINE jsonTypeFromWord #-}++peekJSONType :: (Monoid err) => Parser err JSONType+peekJSONType = UP.orFail (jsonTypeFromWord <$> UP.peekWord)+{-# INLINE peekJSONType #-}++skipNullExpecting :: JSONType -> Parser JSONError a+skipNullExpecting jt =+ UP.asFailure $ nullParser $> ErrorBadType jt JSONTypeNull++-- | Parse a NULL value.+nullParser :: Semigroup err => Parser err ()+nullParser = lexeme $ UP.parseChunk "null" $> ()+{-# INLINE nullParser #-}++skipBoolExpecting :: JSONType -> Parser JSONError a+skipBoolExpecting jt =+ UP.asFailure $+ boolParser $> ErrorBadType jt JSONTypeBool+{-# INLINE skipBoolExpecting #-}++boolParser :: (Monoid err) => Parser err Bool+boolParser =+ lexeme $+ (UP.parseChunk "true" $> True)+ <|> (UP.parseChunk "false" $> False)+{-# INLINE boolParser #-}++skipTextExpecting :: JSONType -> Parser JSONError a+skipTextExpecting jt =+ UP.asFailure $+ textParser $> ErrorBadType jt JSONTypeText+{-# INLINE skipTextExpecting #-}++textParser :: (Monoid err) => Parser err T.Text+textParser = lexeme $ do+ UP.specificWord 34+ parseAfterQuote+{-# INLINE textParser #-}++sepByVoid :: Alternative f => f a1 -> f a2 -> f ()+sepByVoid elem sep = void $ sepBy (void elem) sep+{-# INLINE sepByVoid #-}++skipNumber :: (Monoid err) => Parser err ()+skipNumber = void scientific+{-# INLINE skipNumber #-}++skipNumberExpecting :: JSONType -> Parser JSONError a+skipNumberExpecting jt =+ UP.asFailure $+ skipNumber $> ErrorBadType jt JSONTypeNumber++skipAnything :: Monoid err => Parser err ()+skipAnything = do+ r <- UP.peekWord+ if+ | r == 110 -> lexeme $ UP.parseChunk "null"+ | r == 116 -> lexeme $ UP.parseChunk "true" -- t -> true+ | r == 102 -> lexeme $ UP.parseChunk "false"+ | r == 34 -> void textParser -- " -> text+ | r == 123 -> skipObject -- { -> object+ | r == 91 -> skipArray -- [ -> array+ | r == 45 || (r >= 48 && r <= 57) -> skipNumber+ | otherwise -> (orFail $ pure Nothing)+{-# INLINE skipAnything #-}++skipArray :: (Monoid err) => Parser err ()+skipArray = do+ startArray+ sepByVoid skipAnything comma+ endArray+{-# INLINE skipArray #-}++kvSep :: Semigroup err => Parser err ()+kvSep = lexeme $ UP.specificWord 58++skipAnyKV :: Monoid err => Parser err ()+skipAnyKV = do+ textParser+ kvSep+ skipAnything++comma :: Semigroup err => Parser err ()+comma = lexeme $ UP.specificWord 44++skipObject :: Monoid err => Parser err ()+skipObject = do+ lexeme $ UP.specificWord 123+ sepByVoid skipAnyKV comma+ lexeme $ UP.specificWord 125+{-# INLINE skipObject #-}++failOnError :: (Monoid err) => Either a T.Text -> Parser err T.Text+failOnError = \case+ Left _ -> failParse+ Right txt -> pure txt++parseAfterQuote :: (Monoid err) => Parser err T.Text+parseAfterQuote = do+ chunk <- UP.takeWord8Cont (\c -> c /= 92 && c /= 34) decodeUtf8'+ decoded <- failOnError chunk+ (lexeme (specificWord 34) $> decoded) <|> do+ specificWord 92+ escape <- parseEscape+ res <- parseAfterQuote+ pure $ decoded <> escape <> res+{-# INLINE parseAfterQuote #-}++hexDigit :: Semigroup err => Parser err Word8+hexDigit = do+ r <- UP.word+ orFail $+ if+ | r >= 48 && r <= 57 -> pure $ Just (r - 48)+ | r >= 97 && r <= 103 -> pure $ Just ((r - 97) + 10)+ | otherwise -> pure Nothing+{-# INLINE hexDigit #-}++parseEscape :: (Monoid err) => UP.Parser err T.Text+parseEscape =+ quote+ <|> backslash+ <|> solidus+ <|> backspace+ <|> formfeed+ <|> linefeed+ <|> carriage+ <|> tab+ <|> unicode+ where+ quote = specificWord 34 $> "\""+ backslash = specificWord 92 $> "\\"+ solidus = specificWord 47 $> "/"+ backspace = specificWord 98 $> "\b"+ formfeed = specificWord 102 $> "\f"+ linefeed = specificWord 110 $> "\n"+ carriage = specificWord 114 $> "\r"+ tab = specificWord 116 $> "\t"+ unicode = do+ specificWord 117+ a <- hexDigit+ b <- hexDigit+ c <- hexDigit+ d <- hexDigit+ let res = (((fromIntegral a * 16) + fromIntegral b) * 16 + fromIntegral c) * 16 + fromIntegral d+ pure $ T.pack [chr res]+{-# INLINE parseEscape #-}++parseCharInText (c :: Char) = parseLit c <|> escaped c+ where+ parseLit = \case+ '\\' -> UP.parseChunk "\\\\"+ '"' -> UP.parseChunk "\\\""+ '/' -> UP.parseChunk "/" <|> UP.parseChunk "\\/"+ '\b' -> UP.parseChunk "\\b"+ '\f' -> UP.parseChunk "\\f"+ '\n' -> UP.parseChunk "\\n"+ '\r' -> UP.parseChunk "\\r"+ '\t' -> UP.parseChunk "\\t"+ a -> if isControl a then empty else UP.parseChunk (encodeUtf8 $ T.singleton a)+ escaped c = UP.parseChunk $ encodeUtf8 $ "\\u" <> T.justifyRight 4 '0' (T.pack $ showHex (ord c) mempty)+{-# INLINE parseCharInText #-}++parseSpecificKeyInQuotes :: Monoid err => T.Text -> Parser err ()+parseSpecificKeyInQuotes t = UP.specificWord 34 *> parseSpecificKeyAfterQuote t+{-# INLINE parseSpecificKeyInQuotes #-}++parseSpecificKeyAfterQuote :: Monoid err => T.Text -> Parser err ()+parseSpecificKeyAfterQuote key = (parseRaw <|> parseChars) *> lexeme (UP.specificWord 34)+ where+ parseChars = T.foldr (\c a -> parseCharInText c *> a) (pure ()) key+ parseRaw =+ if isJust $ T.findIndex invalidTextChar key+ then empty+ else UP.parseChunk (encodeUtf8 key)++startBracket :: Semigroup err => Parser err ()+startBracket = lexeme $ UP.specificWord 123++endBracket :: Semigroup err => Parser err ()+endBracket = lexeme $ UP.specificWord 125++startArray :: Semigroup err => Parser err ()+startArray = lexeme $ UP.specificWord 91++endArray :: Semigroup err => Parser err ()+endArray = lexeme $ UP.specificWord 93++parseObjectKV :: Monoid err => T.Text -> Parser err b -> Parser err b+parseObjectKV key v = do+ lexeme $ parseSpecificKeyInQuotes key+ lexeme $ UP.specificWord 58+ v++invalidTextChar :: Char -> Bool+invalidTextChar c =+ c == '"'+ || c == '\\'+ || isControl c++data SP = SP !Integer {-# UNPACK #-} !Int++-- A sub-quadratic algorithm for Integer. Pairs of adjacent radix b+-- digits are combined into a single radix b^2 digit. This process is+-- repeated until we are left with a single digit. This algorithm+-- performs well only on large inputs, so we use the simple algorithm+-- for smaller inputs.+valInteger :: Integer -> Int -> [Integer] -> Integer+valInteger = go+ where+ go :: Integer -> Int -> [Integer] -> Integer+ go _ _ [] = 0+ go _ _ [d] = d+ go b l ds+ | l > 40 = b' `seq` go b' l' (combine b ds')+ | otherwise = valSimple b ds+ where+ -- ensure that we have an even number of digits+ -- before we call combine:+ ds' = if even l then ds else 0 : ds+ b' = b * b+ l' = (l + 1) `quot` 2++ combine b (d1 : d2 : ds) = d `seq` (d : combine b ds)+ where+ d = d1 * b + d2+ combine _ [] = []+ combine _ [_] = errorWithoutStackTrace "this should not happen"+{-# INLINE valInteger #-}++-- The following algorithm is only linear for types whose Num operations+-- are in constant time.+valSimple :: Integer -> [Integer] -> Integer+valSimple base = go 0+ where+ go r [] = r+ go r (d : ds) = r' `seq` go r' ds+ where+ r' = r * base + fromIntegral d+{-# INLINE valSimple #-}++isDigitWord8 :: Word8 -> Bool+isDigitWord8 c = c >= 48 && c <= 57+{-# INLINE isDigitWord8 #-}++decimal0 :: Semigroup err => Parser err Integer+decimal0 = do+ let zero = 48+ digits <- UP.takeWord8 isDigitWord8+ let !length = BS.length digits+ when (length == 0) UP.failParse+ if length > 1 && BS.unsafeHead digits == zero+ then UP.failParse+ else return (bsToInteger digits)+{-# INLINE decimal0 #-}++scientific :: (Monoid err) => UP.Parser err Scientific+scientific = lexeme $ do+ let minus = 45+ plus = 43+ sign <- UP.peekWord+ let !positive = sign == plus || sign /= minus+ when (sign == plus || sign == minus) $+ void UP.word+ n <- decimal0+ let f fracDigits =+ SP+ (BS.foldl' step n fracDigits)+ (negate $ BS.length fracDigits)+ step a w = a * 10 + fromIntegral (w - 48)+ dotty <- UP.peekWordMaybe+ SP c e <- case dotty of+ Just 46 -> UP.word *> UP.takeWord81Cont isDigitWord8 f+ _ -> pure (SP n 0)+ let !signedCoeff+ | positive = c+ | otherwise = - c+ ( (UP.specificWord 101 <|> UP.specificWord 69)+ *> fmap (Scientific.scientific signedCoeff . (e +)) (UP.signed (snd <$> UP.parseIntegral))+ )+ <|> pure (Scientific.scientific signedCoeff e)+{-# INLINE scientific #-}
− lib/Jordan/FromJSON/Megaparsec.hs
@@ -1,295 +0,0 @@-{-# LANGUAGE ApplicativeDo #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE DerivingVia #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeSynonymInstances #-}-module Jordan.FromJSON.Megaparsec- where--import Control.Applicative-import Control.Applicative.Combinators (sepBy)-import qualified Data.ByteString as ByteString-import Data.Char (chr, digitToInt, isControl, isHexDigit, ord)-import Data.Foldable (asum, traverse_)-import Data.Functor (void, ($>))-import Data.List (intercalate)-import Data.Monoid (Alt(..))-import Data.Scientific (Scientific(..))-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Encoding-import Data.Void (Void)-import Data.Word (Word8)-import Debug.Trace (trace, traceM)-import Jordan.FromJSON.Class-import Jordan.FromJSON.ParseInternal-import Numeric (showHex)-import Text.Megaparsec ((<?>))-import qualified Text.Megaparsec as T-import qualified Text.Megaparsec.Char as Char-import qualified Text.Megaparsec.Char.Lexer as Lexer--type Parser = T.Parsec ErrorContext Text.Text-type ParseError = T.ParseErrorBundle Text.Text ErrorContext--newtype ErrorContext- = ErrorContext { getErrorContext :: [Text.Text] }- deriving (Show, Eq, Ord)--instance T.ShowErrorComponent ErrorContext where- showErrorComponent- = intercalate ", "- . fmap (("in " ++) . Text.unpack)- . getErrorContext--newtype ObjectParser a = ObjectParser { getObjectParser :: Permutation Parser a }- deriving newtype (Functor, Applicative)--newtype ArrayParser a- = ArrayParser { getArrayParser :: Parser a }- deriving (Functor)--instance Applicative ArrayParser where- pure = ArrayParser . pure- (ArrayParser f) <*> (ArrayParser a) = ArrayParser $- (f <* comma) <*> a--lexeme :: Parser a -> Parser a-lexeme = Lexer.lexeme $ Lexer.space Char.space1 empty empty--takeSpace :: Parser ()-takeSpace = void $ many Char.space1--parseAnyField :: Parser ()-parseAnyField = T.label "an extraneous object field we do not care about" $ do- T.label "ignored object key" parseJSONText- lexeme $ Char.char ':'- lexeme consumeJunkValue--objectKey :: Text.Text -> Parser ()-objectKey k = T.label ("object key '" <> Text.unpack k <> "'") $ lexeme $ do- Char.char '"'- T.label "object label" $- Text.foldr (\c a -> parseCharInText c *> a) (pure ()) k- Char.char '"'- pure ()--parseCharInText :: Char -> Parser ()-parseCharInText a = parseLit a <|> escaped a- where- parseLit :: Char -> Parser ()- parseLit = \case- '\\' -> void $ T.chunk "\\\\"- '"' -> void $ T.chunk "\\\""- '/' -> void $ T.chunk "/" <|> T.chunk "\\/"- '\b' -> void $ T.chunk "\\b"- '\f' -> void $ T.chunk "\\f"- '\n' -> void $ T.chunk "\\n"- '\r' -> void $ T.chunk "\\r"- '\t' -> void $ T.chunk "\\t"- a -> if isControl a then empty else void $ T.single a- escaped :: Char -> Parser ()- escaped a = void $ T.chunk $ Text.pack $ withEscaped $ (showHex $ ord a) []- withEscaped :: String -> String- withEscaped a@[_] = "\\u000" <> a- withEscaped a@[_,_] = "\\u00" <> a- withEscaped a@[_,_,_] = "\\u0" <> a- withEscaped r = "\\u" <> r--parseDictField- :: Parser a- -> Parser (Text.Text, a)-parseDictField valParser = do- key <- parseJSONText- labelSep- val <- valParser- pure (key, val)--parseObjectField- :: Text.Text- -> Parser a- -> Parser a-parseObjectField t f = do- T.try $ objectKey t- labelSep- lexeme f--parseJSONText :: Parser Text.Text-parseJSONText = lexeme $ do- T.try $ Char.char '"'- innerText--innerText :: Parser Text.Text-innerText = do- chunk <- T.takeWhileP Nothing $ \char -> char /= '\\' && char /= '"'- l <- T.lookAhead $ T.option Nothing (Just <$> T.anySingle)- case l of- Nothing -> fail "string without end"- Just '"' -> do- T.label "quotation mark" T.anySingle- pure chunk- Just '\\' -> do- T.anySingle- r <- parseEscape- rest <- innerText- pure $ chunk <> r <> rest- Just _ -> fail "IMPOSSIBLE"--parseEscape :: Parser Text.Text-parseEscape- = quote- <|> backslash- <|> solidus- <|> backspace- <|> formfeed- <|> linefeed- <|> carriage- <|> tab- <|> escapedUnicode- where- backslash = T.chunk "\\"- quote = T.chunk "\""- solidus = T.chunk "/" $> "/"- backspace = T.chunk "b" $> "\b"- formfeed = T.chunk "f" $> "\f"- linefeed = T.chunk "n" $> "\n"- carriage = T.chunk "r" $> "\r"- tab = T.chunk "t" $> "\t"- escapedUnicode = T.label "unicode escape code" $ do- Char.char 'u'- a <- parseHexDigit- b <- parseHexDigit- c <- parseHexDigit- d <- parseHexDigit- let s = (((a * 16) + b) * 16 + c) * 16 + d- pure $ Text.pack [chr s]--parseHexDigit :: Parser Int-parseHexDigit = digitToInt <$> T.satisfy isHexDigit--comma :: Parser ()-comma = void $ lexeme $ Char.char ','--labelSep :: Parser ()-labelSep = void $ lexeme $ Char.char ':'--parseAnyObject :: Parser ()-parseAnyObject = T.label "Ignored object" $ do- T.try $ lexeme $ Char.char '{'- parseAnyField `sepBy` comma- lexeme $ Char.char '}'- pure ()--parseAnyArray :: Parser ()-parseAnyArray = T.label "Ignored array" $ do- T.try $ lexeme $ Char.char '['- consumeJunkValue `sepBy` comma- lexeme $ Char.char ']'- pure ()--consumeJunkValue :: Parser ()-consumeJunkValue- = void parseAnyObject- <|> void parseAnyArray- <|> void parseJSONText- <|> void parseJSONNumber- <|> void parseJSONNull--parseJSONNumber :: Parser Scientific-parseJSONNumber = Lexer.signed (pure ()) Lexer.scientific--parseJSONNull :: Parser ()-parseJSONNull = void $ lexeme $ T.chunk "null"--parseJSONBool :: Parser Bool-parseJSONBool = lexeme $ (T.chunk "true" $> True) <|> (T.chunk "false" $> False)--junkFieldsAtEnd :: Parser ()-junkFieldsAtEnd = T.label "misc fields after parsing is done" $ do- comma- parseAnyField `sepBy` comma- pure ()--newtype MegaparsecParser a- = MegaparsecParser { getMegaparsecParser :: Parser a }- deriving (Functor)- deriving (Monoid) via (Alt Parser a)--instance Semigroup (MegaparsecParser a) where- (MegaparsecParser a) <> (MegaparsecParser b) = MegaparsecParser $ T.try a <|> T.try b--instance JSONObjectParser ObjectParser where- parseFieldWith label- = ObjectParser- . asPermutation- . T.label ("field " <> Text.unpack label)- . parseObjectField label- . getMegaparsecParser--instance JSONTupleParser ArrayParser where- consumeItemWith = ArrayParser . getMegaparsecParser--instance JSONParser MegaparsecParser where- parseObject name p = MegaparsecParser $ T.label (Text.unpack name <> " object") $ do- T.label "object start" $ lexeme $ Char.char '{'- r <- wrapEffect parseAnyField comma $ getObjectParser p- T.label "object end" $ T.optional junkFieldsAtEnd- lexeme $ Char.char '}'- pure r- parseDictionary valParser = MegaparsecParser $ T.label "dictionary" $ do- lexeme $ Char.char '{'- r <- parseDictField (getMegaparsecParser valParser) `sepBy` comma- lexeme $ Char.char '}'- pure r- parseTuple p = MegaparsecParser $ do- lexeme $ T.label "Array start" $ Char.char '['- r <- getArrayParser p- lexeme $ T.label "Array end" $ Char.char ']'- pure r- parseArrayWith p = MegaparsecParser $ do- lexeme $ T.label "Array start" $ Char.char '['- r <- getMegaparsecParser p `sepBy` comma- lexeme $ T.label "Array end" $ Char.char ']'- pure r- parseTextConstant t = MegaparsecParser $ T.label "text constant" $ do- Char.char '"'- Text.foldr (\c a -> parseCharInText c *> a) (pure ()) t- Char.char '"'- pure ()- parseText = MegaparsecParser parseJSONText- parseBool = MegaparsecParser parseJSONBool- parseNumber = MegaparsecParser parseJSONNumber- parseNull = MegaparsecParser $ T.label "null literal" parseJSONNull- validateJSON (MegaparsecParser f) = MegaparsecParser $ do- r <- f- case r of- Left a -> fail (Text.unpack a)- Right a -> pure a---- | Convert an abstract JSONParser to a Megaparsec parser.-convertParserToMegaparsecParser :: (forall parser. JSONParser parser => parser a) -> Parser a-convertParserToMegaparsecParser = getMegaparsecParser---- | Get a megaparsec parser for your JSON value.--- This parser will not construct any intermediate maps or other structures - your object will be parsed directly!------ Note: this parser, until the ones that are built into the class, can consume whitespace at the start of the JSON.-megaparsecParser :: (FromJSON val) => Parser val-megaparsecParser = takeSpace *> getMegaparsecParser fromJSON---- | Run an abstract JSONParser via Megaparsec.-runParserViaMegaparsec :: (forall parser. JSONParser parser => parser a) -> Text.Text -> Either String a-runParserViaMegaparsec p t =- case T.runParser (convertParserToMegaparsecParser p) "" t of- Left r -> Left $ T.errorBundlePretty r- Right k -> Right k---- | Parse an object for which 'FromJSON' is defined via Megaparsec.-parseViaMegaparsec :: forall val. (FromJSON val) => Text.Text -> Either String val-parseViaMegaparsec = runParserViaMegaparsec fromJSON
− lib/Jordan/FromJSON/ParseInternal.hs
@@ -1,120 +0,0 @@-{-# LANGUAGE ApplicativeDo #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}--- | Module containing internal helpers for our parsers.-module Jordan.FromJSON.ParseInternal- where--import Control.Applicative (Alternative(..))-import Data.Foldable (asum)---- | A parser for permutations.------ Based on the paper Parsing Permutation Phrases by--- Arthur Baars, Andres Loh, and S. Doaitse Swierstra.------ The source code for 'Control.Applicative.Permutations' really helped--- in writing this, although this type is structured differently (and closer to the actual paper).--- Thank you very much to Alex Washburn!-data Permutation parser a- = Choice [Branch parser a]- -- ^ We have multiple options for how to parse further.- | Empty a- -- ^ We have reached the end and only have a single value.---- | A branch of a permutation.--- Permutation parsers work by building up the entire tree of--- possible parsers, which is efficient in Haskell due to laziness.-data Branch parser a- = forall arg. Branch (Permutation parser (arg -> a)) (parser arg)--instance (Functor m) => Functor (Branch m) where- fmap f (Branch perm p) = Branch (fmap (f .) perm) p--instance (Functor m) => Functor (Permutation m) where- fmap f = \case- Choice c -> Choice $ fmap f <$> c- Empty a -> Empty (f a)--instance (Alternative m) => Applicative (Branch m) where- pure a = Branch (pure $ const a) (pure ())- (Branch permuteF argF) <*> (Branch permuteA argA) =- Branch (args <$> permuteF <*> permuteA) arguments- where- arguments = ((,) <$> argA <*> argF) <|> (flip (,) <$> argF <*> argA)- args :: (arg1 -> a -> b) -> (arg2 -> a) -> (arg2, arg1) -> b- args f a (aa, fa) = f fa (a aa)--instance (Alternative m) => Applicative (Permutation m) where- pure = Empty- (Empty f) <*> (Empty a) = Empty $ f a- (Empty f) <*> (Choice choices) = Choice $ fmap f <$> choices- (Choice f) <*> (Empty a) = Choice $ fmap ($ a) <$> f- t1@(Choice bs1) <*> t2@(Choice bs2) = Choice (map ins2 bs1 ++ map ins1 bs2)- where- ins1 (Branch perm p) = Branch ((.) <$> t1 <*> perm) p- ins2 (Branch perm p) = Branch (flip <$> perm <*> t2) p---- | Wrap up a permutation parser with two effects:------ It will first interleave an infinite number of some effects, which represent parsing "junk" or unwanted fields.--- At every stage of the permutation, we will first try to run the effect we want, and if it fails--- we will try to run the "junk" effect instead, then try again.------ We attempt to *intersperse* the second effect afterwards.--- It adds a new effect between every effect.--- This is used in parsing JSON to add commas.-wrapEffect- :: forall m a b. (Alternative m)- => m b- -- ^ Consume a single, \"junk\" field.- -- Used to ignore JSON keys that we do not care about.- -> m b- -- ^ Consume a \"separator\" between items in the permutation.- -- This consumption is not done at the front of the permutation- -- or after the end of it.- -- This is used to parse commas between JSON fields.- -> Permutation m a- -- ^ The permutation parser to run.- -> m a- -- ^ The final parser.-wrapEffect takeSingle effAfter (Empty a) = pure a-wrapEffect takeSingle effAfter (Choice choices) = consumeMany- where- consumeMany- = asum (pars <$> choices)- -- Base case above: one of the choices of the permutation matched- <|> (takeSingle *> effAfter *> consumeMany)- -- Interleaving case: none of the choices of the permutation matched,- -- so run a "junk" effect, the separator, and try again.- -- Due to the recursion here we will do this infinitely until we either cannot- -- run the junk effect, *or* we have a field that matches one of the choices of the permutation.- runWithEffect :: Permutation m whatever -> m whatever- runWithEffect (Empty a) = pure a- runWithEffect (Choice choices) = effAfter *> consumeRec- where- consumeRec- = asum (pars <$> choices)- -- Run one of the effects from the permutation- <|> (takeSingle *> effAfter *> consumeRec)- -- Interleave a potentially infinite number of junk effects, with the separator effect between them.- pars :: Branch m whatever -> m whatever- pars (Branch perm arg) = do- a <- arg- rest <- runWithEffect perm- pure $ rest a--asParser :: (Alternative f) => Permutation f a -> f a-asParser (Empty a) = pure a-asParser (Choice choices) = asum (pars <$> choices)- where- pars :: (Alternative f) => Branch f a -> f a- pars (Branch perm arg) = do- a <- arg- rest <- asParser perm- pure $ rest a--asPermutation :: (Alternative f) => f a -> Permutation f a-asPermutation p = Choice $ pure $ Branch (pure id) p
+ lib/Jordan/FromJSON/UnboxedReporting.hs view
@@ -0,0 +1,246 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE NoMonomorphismRestriction #-}++-- | Attempts to parse to either a result type or a direct report, by using a custom parser.+-- This parser uses Haskell primops to try to avoid allocations.+-- At the end of the day, it's not as fast as Attoparsec, but it's pretty dang fast.+--+-- We could not use Attoparsec directly due to the need for differnet error handling.+-- Other libraries with correct error handling behavior do exist, but in order to keep the dependency footprint low,+-- we rolled our own.+module Jordan.FromJSON.UnboxedReporting (parseOrReportWith, parseOrReport) where++import Control.Applicative (Alternative (empty, (<|>)))+import Control.Applicative.Combinators (sepBy)+import Control.Monad (when)+import Data.Bifunctor+import qualified Data.ByteString as BS+import Data.ByteString.Unsafe as BS+import Data.Char (chr, isControl, ord)+import Data.Functor (void, ($>))+import qualified Data.Map.Strict as Map+import Data.Maybe (isJust)+import Data.Monoid (Alt (..))+import Data.Scientific (Scientific)+import qualified Data.Scientific as Scientific+import qualified Data.Text as T+import Data.Text.Encoding (decodeUtf8', encodeUtf8)+import Data.Word (Word8)+import Jordan.FromJSON.Class+import Jordan.FromJSON.Internal.Attoparsec (bsToInteger)+import Jordan.FromJSON.Internal.Permutation+import Jordan.FromJSON.Internal.UnboxedParser as UP hiding (AccumE (..), AccumEL, AccumER)+import Jordan.FromJSON.Internal.UnboxedReporting+import Jordan.Types.Internal.AccumE (AccumE (AccumE))+import Jordan.Types.JSONError+ ( JSONArrayError (..),+ JSONError+ ( ErrorBadArray,+ ErrorBadObject,+ ErrorBadTextConstant,+ ErrorBadType,+ ErrorInvalidJSON,+ ErrorMesage,+ ErrorNoValue+ ),+ JSONObjectError (..),+ )+import Jordan.Types.JSONType (JSONType (..))+import Numeric (showHex)++newtype ReportingParser a = ReportingParser {runReportingParser :: UP.Parser JSONError a}+ deriving (Functor) via (UP.Parser JSONError)+ deriving (Semigroup) via (Alt (UP.Parser JSONError) a)++newtype ReportingObjectParser a = ReportingObjectParser+ {runReportingObjectParser :: Permutation (UP.Parser JSONObjectError) a}+ deriving (Functor, Applicative) via (Permutation (UP.Parser JSONObjectError))++newtype ReportingTupleParser a = ReportingTupleParser+ {runReportingTupleParser :: Integer -> (Integer, UP.Parser JSONArrayError a)}++instance Functor ReportingTupleParser where+ fmap f (ReportingTupleParser cb) =+ ReportingTupleParser $ \index -> second (f <$>) $ cb index++instance Applicative ReportingTupleParser where+ pure a = ReportingTupleParser (,pure a)+ (ReportingTupleParser f) <*> (ReportingTupleParser a) =+ ReportingTupleParser $ \index ->+ let (index', fp) = f index+ (index'', ap) = a index+ in ( index'',+ do+ f' <- fp+ when (index /= index' && index /= index'') comma+ f' <$> ap+ )++toObjectParser :: T.Text -> Parser JSONError a -> ReportingObjectParser a+toObjectParser field itemParser =+ ReportingObjectParser $+ asPermutationWithFailing parseKV failNoValue+ where+ failNoValue = do+ r <- UP.peekRest+ UP.failWith $+ MkJSONObjectError $ Map.singleton field ErrorNoValue+ parseKV = first (MkJSONObjectError . Map.singleton field) $ parseObjectKV field itemParser+{-# INLINE toObjectParser #-}++toObjectParserDef field itemParser def =+ ReportingObjectParser $+ asPermutationWithDefault parseKV def+ where+ parseKV = first (MkJSONObjectError . Map.singleton field) $ do+ parseObjectKV field itemParser++parseArrayInner :: UP.Parser JSONError a -> Integer -> UP.Parser JSONArrayError [a]+parseArrayInner parse index =+ ((:) <$> parseElem <*> ((comma *> parseArrayInner parse (index + 1)) <|> pure []))+ <|> pure []+ where+ parseElem = first (MkJSONArrayError . Map.singleton index) parse+{-# INLINE parseArrayInner #-}++parseDictKey :: UP.Parser JSONError a -> UP.Parser JSONObjectError (T.Text, a)+parseDictKey parseVal = do+ key <- textParser+ kvSep+ val <- first (MkJSONObjectError . Map.singleton key) parseVal+ pure (key, val)++instance JSONTupleParser ReportingTupleParser where+ consumeItemWith = \(ReportingParser itemParser) ->+ ReportingTupleParser $+ \index ->+ (index + 1, first (MkJSONArrayError . Map.singleton index) itemParser)++instance JSONObjectParser ReportingObjectParser where+ parseFieldWith field = \(ReportingParser itemParser) ->+ toObjectParser field itemParser+ parseFieldWithDefault field = \(ReportingParser itemParser) def ->+ toObjectParserDef field itemParser def++instance JSONParser ReportingParser where+ parseTuple (ReportingTupleParser tp) =+ ReportingParser $ do+ jt <- peekJSONType+ case jt of+ JSONTypeArray -> tuple+ other -> skipWithFailure $ ErrorBadType JSONTypeArray other+ where+ tuple = do+ startArray+ let (_, arrayParse) = tp 0+ arr <- first ErrorBadArray arrayParse+ endArray+ pure arr+ {-# INLINE parseTuple #-}+ parseTextConstant tc =+ ReportingParser $ do+ jt <- peekJSONType+ case jt of+ JSONTypeText -> textConstant+ other -> skipWithFailure $ ErrorBadType JSONTypeText other+ where+ textConstant = do+ r <- UP.specificWord 34+ void (parseSpecificKeyAfterQuote tc) <|> do+ r <- parseAfterQuote+ UP.failWith (ErrorBadTextConstant tc r)+ {-# INLINE parseTextConstant #-}+ parseArrayWith (ReportingParser rp) =+ ReportingParser $+ array+ <|> skipNullExpecting JSONTypeArray+ <|> skipBoolExpecting JSONTypeArray+ <|> skipTextExpecting JSONTypeArray+ <|> skipNumberExpecting JSONTypeArray+ where+ array = do+ startArray+ arr <- first ErrorBadArray $ parseArrayInner rp 0+ endArray+ pure arr+ {-# INLINE parseDictionary #-}+ parseDictionary (ReportingParser dict) =+ ReportingParser $ do+ jt <- peekJSONType+ case jt of+ JSONTypeObject -> parseDict+ other -> UP.asFailure $ skipAnything $> ErrorBadType JSONTypeObject other+ where+ parseDict = do+ startBracket+ r <- first ErrorBadObject $ parseDictKey dict `sepBy` comma+ endBracket+ pure r+ parseObject (ReportingObjectParser permute) = ReportingParser $ do+ r <- peekJSONType+ case r of+ JSONTypeObject -> po+ other -> UP.asFailure $ skipAnything $> ErrorBadType JSONTypeObject other+ where+ po = first ErrorBadObject $ do+ startBracket+ a <-+ wrapEffect+ skipAnyKV+ comma+ permute+ rest <- peekRest+ endBracket <|> do+ comma+ skipAnyKV `sepByVoid` comma+ endBracket+ pure a+ {-# INLINE parseObject #-}+ parseNull =+ ReportingParser $ do+ jt <- peekJSONType+ case jt of+ JSONTypeNull -> nullParser+ other -> skipWithFailure $ ErrorBadType JSONTypeNull other+ {-# INLINE parseNull #-}+ parseBool = ReportingParser $ do+ jt <- peekJSONType+ case jt of+ JSONTypeBool -> boolParser+ other -> skipWithFailure $ ErrorBadType JSONTypeBool other+ {-# INLINE parseBool #-}+ parseText = ReportingParser $ do+ jt <- peekJSONType+ case jt of+ JSONTypeText -> textParser+ other -> skipWithFailure $ ErrorBadType JSONTypeText other+ {-# INLINE parseText #-}+ parseNumber =+ ReportingParser $ do+ r <- peekJSONType+ case r of+ JSONTypeNumber -> scientific+ other -> UP.asFailure $ skipAnything $> ErrorBadType JSONTypeNumber other+ {-# INLINE parseNumber #-}+ validateJSON (ReportingParser rp) =+ ReportingParser $+ lowerErr (fmap (first ErrorMesage) rp)+ {-# INLINE validateJSON #-}++parseOrReportWith ::+ (forall parser. JSONParser parser => parser a) ->+ BS.ByteString ->+ Either JSONError a+parseOrReportWith (ReportingParser rp) bs =+ case UP.parseBS (UP.skipWhitespace *> rp) bs of+ Nothing -> Left ErrorInvalidJSON+ Just (AccumE r) -> r+{-# INLINE parseOrReportWith #-}++parseOrReport :: (FromJSON a) => BS.ByteString -> Either JSONError a+parseOrReport = parseOrReportWith fromJSON+{-# INLINE parseOrReport #-}
lib/Jordan/Generic/Options.hs view
@@ -1,12 +1,83 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-}-module Jordan.Generic.Options- where+{-# LANGUAGE GADTs #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuantifiedConstraints #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} -import GHC.Generics (Generic)+module Jordan.Generic.Options where +import Data.Coerce+import Data.Text (Text)+import qualified Data.Text as T+import Data.Type.Bool+import Data.Typeable (TypeRep, splitTyConApp, tyConModule, tyConName)+import GHC.Exts (Constraint)+import GHC.Generics+import GHC.TypeLits++type Representational (f :: * -> *) =+ (forall a b. (Coercible a b) => Coercible (f a) (f b) :: Constraint)+ data SumTypeEncoding = TagVal | TagInField deriving (Show, Read, Eq, Ord, Bounded, Enum, Generic) -newtype PartOfSum f a = PartOfSum { getPartOfSum :: f a }+type family AllNullary cons where+ AllNullary (C1 ('MetaCons _ _ 'False) (S1 ('MetaSel 'Nothing _ _ _) U1)) = True+ AllNullary (a :+: b) = AllNullary a && AllNullary b+ AllNullary _ = False++newtype PartOfSum f a = MkPartOfSum {getPartOfSum :: f a}+ deriving (Show, Read, Eq, Ord, Generic)++-- | A newtype wrapper, designed to make it easier to derive ToJSON and FromJSON instances.+-- The API of abstract JSON serializing is awkward due to the somewhat bad ergonomics of the+-- 'Data.Functor.Contravariant.Divisible.Divisible' and (especially)+-- 'Data.Functor.Contravariant.Divisible.Decidable' typeclasses.+--+-- In general, using @ -XDerivingVia @, @ -XDeriveGeneric @, @ -XDataKinds @ and this wrapper will make your life much easier.+-- Unfortunately, due to a weird GHC quirk, you also need @ -XDerivingVia @.+--+-- That is, the following won't work, complaining about role errors:+--+-- @+-- data PersonFilter = PersonFilter { filterFirstName :: Maybe Text, filterLastName :: Maybe Text }+-- deriving (Show, Generic)+-- deriving (ToJSON, FromJSON) via (WithOptions '[KeepNothingFields] PersonFilter)+-- @+--+-- But this will:+--+-- @+-- data PersonFilter = PersonFilter { filterFirstName :: Maybe Text, filterLastName :: Maybe Text }+-- deriving (Show, Generic)+--+-- deriving via (WithOptions '[KeepNothingFields] PersonFilter) instance (ToJSON PersonFilter)+-- deriving via (WithOptions '[KeepNothingFields] PersonFilter) instance (FromJSON PersonFilter)+-- @+newtype WithOptions (options :: [*]) a = WithOptions {getWithOptions :: a}+ deriving (Show, Eq, Ord)++-- | Newtype for use with GeneralizedNewtypeDeriving.+-- Will have us omit Nothing fields for parsing and serializing.+data OmitNothingFields = OmitNothingFields++-- | Keep nothing fields.+-- Will have us omit @ null @ when serializing Maybe types.+data KeepNothingFields = KeepNothingFields++fullyQualifyName ::+ TypeRep ->+ Text+fullyQualifyName tr =+ case splitTyConApp tr of+ (tc, []) -> baseName tc+ (tc, args) -> baseName tc <> "(" <> T.intercalate "," (fullyQualifyName <$> args) <> ")"+ where+ baseName tc = T.pack (tyConModule tc <> "." <> tyConName tc)
lib/Jordan/ToJSON/Builder.hs view
@@ -2,11 +2,13 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-}+ module Jordan.ToJSON.Builder- ( JSONBuilder (..)- , toJSONViaBuilder- , toJSONAsBuilder- ) where+ ( JSONBuilder (..),+ toJSONViaBuilder,+ toJSONAsBuilder,+ )+where import Data.ByteString.Builder (Builder, toLazyByteString) import qualified Data.ByteString.Builder.Prim as BP@@ -23,8 +25,7 @@ -- | JSON Serializer that makes use of 'Data.ByteString.Builder' to do its work. -- Should be really fast.-newtype JSONBuilder a- = JSONBuilder { runJSONBuilder :: a -> Builder }+newtype JSONBuilder a = JSONBuilder {runJSONBuilder :: a -> Builder} deriving (Semigroup, Monoid) via (a -> Builder) instance Contravariant JSONBuilder where@@ -46,39 +47,41 @@ runCommaSep Empty = "" runCommaSep (Written w) = w -newtype JSONCommaBuilder a- = JSONCommaBuilder { runCommaBuilder :: a -> CommaSep }+newtype JSONCommaBuilder a = JSONCommaBuilder {runCommaBuilder :: a -> CommaSep} -- Lifted from aeson (thanks to them!) ascii2 :: (Char, Char) -> BP.BoundedPrim a ascii2 cs = BP.liftFixedToBounded $ const cs BP.>$< BP.char7 BP.>*< BP.char7 {-# INLINE ascii2 #-} --- | Also lifted from Aeson (once again, thanks to them!)+-- | Mostly lifted from Aeson (thanks to them). escapeAscii :: BP.BoundedPrim Word8 escapeAscii =- BP.condB (== c2w '\\' ) (ascii2 ('\\','\\')) $- BP.condB (== c2w '\"' ) (ascii2 ('\\','"' )) $- BP.condB (>= c2w '\x20') (BP.liftFixedToBounded BP.word8) $- BP.condB (== c2w '\n' ) (ascii2 ('\\','n' )) $- BP.condB (== c2w '\r' ) (ascii2 ('\\','r' )) $- BP.condB (== c2w '\t' ) (ascii2 ('\\','t' )) $- BP.liftFixedToBounded hexEscape -- fallback for chars < 0x20+ -- Irritatingly we have a few non-control characters we need to escape,+ -- so we try to do that first.+ BP.condB (== 0x5c) (ascii2 ('\\', '\\')) $ -- a backslash+ BP.condB (== 0x22) (ascii2 ('\\', '"')) $ -- a quote+ BP.condB (>= 0x20) (BP.liftFixedToBounded BP.word8) $ -- Now, if we have an ordinal above 0x20, we can just encode directly+ BP.condB (== 0x0a) (ascii2 ('\\', 'n')) $ -- Special control character \n+ BP.condB (== 0x0d) (ascii2 ('\\', 'r')) $ -- special control character \r+ BP.condB (== 0x09) (ascii2 ('\\', 't')) $ -- Special control character \t+ BP.condB (== 0x0c) (ascii2 ('\\', 'f')) $ -- Special control character \f+ BP.condB (== 0x08) (ascii2 ('\\', 'b')) $ -- Special control character \b+ BP.liftFixedToBounded hexEscape -- fallback for other control characters where- c2w :: Char -> Word8- c2w c = fromIntegral (ord c) hexEscape :: BP.FixedPrim Word8- hexEscape = (\c -> ('\\', ('u', fromIntegral c))) BP.>$<- BP.char8 BP.>*< BP.char8 BP.>*< BP.word16HexFixed+ hexEscape =+ (\c -> ('\\', ('u', ('0', ('0', c)))))+ BP.>$< BP.char7 BP.>*< BP.char7 BP.>*< BP.char7 BP.>*< BP.char7 BP.>*< BP.word8HexFixed {-# INLINE escapeAscii #-} -- | Make a builder for a quoted string, which does all the cool escaping crap we need to do. -- Mostly stolen shamelessly from Aeson.-writeQuotedString :: Text -> Builder-writeQuotedString t = "\"" <> encodeUtf8BuilderEscaped escapeAscii t <> "\""+serializeQuotedString :: Text -> Builder+serializeQuotedString t = "\"" <> encodeUtf8BuilderEscaped escapeAscii t <> "\"" -writeKV :: (a -> Builder) -> Text -> a -> Builder-writeKV map k v = writeQuotedString k <> ": " <> map v+serializeKV :: (a -> Builder) -> Text -> a -> Builder+serializeKV map k v = serializeQuotedString k <> ": " <> map v instance Contravariant JSONCommaBuilder where contramap f (JSONCommaBuilder a) = JSONCommaBuilder $ a . f@@ -90,11 +93,14 @@ (b, c) -> runCommaBuilder sB b <> runCommaBuilder sC c instance JSONObjectSerializer JSONCommaBuilder where- writeField field (JSONBuilder a) = JSONCommaBuilder $ \arg ->- Written $ writeQuotedString field <> ": " <> a arg+ serializeFieldWith field (JSONBuilder a) = JSONCommaBuilder $ \arg ->+ Written $ serializeQuotedString field <> ": " <> a arg+ serializeJust field (JSONBuilder a) = JSONCommaBuilder $ \case+ Nothing -> Empty+ Just a' -> Written $ serializeQuotedString field <> ": " <> a a' instance JSONTupleSerializer JSONCommaBuilder where- writeItem (JSONBuilder a) = JSONCommaBuilder $ Written . a+ serializeItemWith (JSONBuilder a) = JSONCommaBuilder $ Written . a instance Selectable JSONBuilder where giveUp f = JSONBuilder $ \a -> absurd (f a)@@ -104,7 +110,7 @@ Right rhs -> runJSONBuilder serR rhs instance JSONSerializer JSONBuilder where- serializeObject _ (JSONCommaBuilder f) = JSONBuilder $ \a ->+ serializeObject (JSONCommaBuilder f) = JSONBuilder $ \a -> case f a of Written bu -> "{" <> bu <> "}" Empty -> "{}"@@ -113,9 +119,9 @@ Written bu -> "[" <> bu <> "]" Empty -> "[]" serializeDictionary (JSONBuilder t) = JSONBuilder $ \a ->- "{" <> runCommaSep (foldMap (\(k,v) -> Written (writeKV t k v)) a) <> "}"- serializeText = JSONBuilder $ \t -> writeQuotedString t- serializeTextConstant = JSONBuilder . const . writeQuotedString+ "{" <> runCommaSep (foldMap (\(k, v) -> Written (serializeKV t k v)) a) <> "}"+ serializeText = JSONBuilder $ \t -> serializeQuotedString t+ serializeTextConstant = JSONBuilder . const . serializeQuotedString serializeNumber = JSONBuilder $ \a -> scientificBuilder a serializeNull = JSONBuilder $ const "null" serializeBool = JSONBuilder $ \case
lib/Jordan/ToJSON/Class.hs view
@@ -1,29 +1,37 @@+{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-}-module Jordan.ToJSON.Class- where +module Jordan.ToJSON.Class where++import Data.Foldable (fold) import Data.Functor.Contravariant import Data.Functor.Contravariant.Divisible+import Data.List.NonEmpty as NE import qualified Data.Map.Strict as Map import qualified Data.Ratio as Ratio-import Data.Scientific (Scientific)+import Data.Scientific import qualified Data.Scientific as Sci import qualified Data.Semigroup as Semi-import Data.Text (Text)+import qualified Data.Set as Set+import Data.Text (Text, pack) import qualified Data.Text as T-import Data.Typeable (Proxy(..), Typeable, tyConModule, tyConName, typeRep, typeRepTyCon)+import Data.Typeable (Proxy (..), TypeRep, Typeable, splitTyConApp, tyConModule, tyConName, typeRep, typeRepTyCon) import Data.Void (Void, absurd) import GHC.Generics+import GHC.TypeLits (KnownSymbol, symbolVal) import Jordan.Generic.Options -- | Basically just 'Data.Functor.Contravariant.Divisible.Decidable' but without@@ -38,9 +46,13 @@ class (Contravariant f) => Selectable f where -- | Give up trying to decide. giveUp :: (arg -> Void) -> f arg+ -- | Pick one thing, or another, as long as you can serialize both options. select :: (arg -> Either lhs rhs) -> f lhs -> f rhs -> f arg +selected :: (Selectable f) => f lhs -> f rhs -> f (Either lhs rhs)+selected = select id+ -- | An abstract representation of how to serialize a JSON object. -- Since serializing is the exact opposite of parsing, we have to be -- 'Data.Functor.Contravariant.Decidable' instead of 'Control.Applicative.Alternative'.@@ -51,57 +63,87 @@ -- the combinators for putting things together, and involve a lot of tuples everywhere. -- -- Thankfully we provide a good interface to derive these classes generically!-class (Divisible f) => JSONObjectSerializer f where- writeField- :: Text- -- ^ Label for the field to write- -> (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a)- -- ^ How to write the field.+class (Divisible f, Representational f) => JSONObjectSerializer f where+ serializeFieldWith ::+ -- | Label for the field to serialize+ Text ->+ -- | How to serialize the field. -- The forall ensures that JSON serialization is kept completely abstract. -- You can only use the methods of 'JSONSerializer' here.- -> f a+ (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) ->+ f a+ serializeField :: (ToJSON a) => Text -> f a+ serializeField t = serializeFieldWith t toJSON+ serializeDescribeFieldWith ::+ -- | Field key to serialize.+ Text ->+ -- | Field description.+ Text ->+ -- | Serializer for the field.+ (forall valueSerializer. JSONSerializer valueSerializer => valueSerializer a) ->+ f a+ serializeDescribeFieldWith t _ = serializeFieldWith t -class (Divisible f) => JSONTupleSerializer f where- writeItem- :: (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a)- -- ^ Write a single item into the tuple.+ -- | Write if we have Just a value. Do not add the field otherwise.+ serializeJust ::+ -- | Label for the field to serialize+ Text ->+ -- | Serializer for Just+ (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) ->+ f (Maybe a)++class (Divisible f, Representational f) => JSONTupleSerializer f where+ serializeItemWith ::+ -- | Write a single item into the tuple. -- The forall keeps things abstract.- -> f a+ (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) ->+ f a+ serializeItem ::+ (ToJSON a) => f a+ serializeItem = serializeItemWith toJSON -- | An abstract representation of how to serialize a Haskell value into JSON.-class (Selectable f) => JSONSerializer f where- serializeObject- :: Text- -- ^ A name for the object. Should be "globally unique" as much as possible.- -> (forall objSerializer. JSONObjectSerializer objSerializer => objSerializer a)- -- ^ How to serialize the object.+class (Selectable f, Representational f) => JSONSerializer f where+ serializeObject ::+ -- | How to serialize the object. -- The forall here keeps things abstract: you are only allowed to use the methods of 'JSONObjectSerializer' here.- -> f a- serializeDictionary- :: (Foldable t)- => (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a)- -> f (t (Text, a))- serializeText- :: f Text+ (forall objSerializer. JSONObjectSerializer objSerializer => objSerializer a) ->+ f a+ serializeDictionary ::+ (Foldable t) =>+ (forall jsonSerializer. JSONSerializer jsonSerializer => jsonSerializer a) ->+ f (t (Text, a))+ serializeText ::+ f Text+ -- | Serialize some text constant. -- Note that this returns a serializer of anything: if you are always going to serialize out the same string, -- we don't need to even look at the thing we\'re serializing!- serializeTextConstant- :: Text- -> f a- serializeNull- :: f any- serializeNumber- :: f Scientific- serializeBool- :: f Bool- serializeTuple- :: (forall tupleSerializer. JSONTupleSerializer tupleSerializer => tupleSerializer a)- -> f a- serializeArray- :: (ToJSON a)- => f [a]+ serializeTextConstant ::+ Text ->+ f a + serializeNull ::+ f any+ serializeNumber ::+ f Scientific+ serializeBool ::+ f Bool+ serializeTuple ::+ (forall tupleSerializer. JSONTupleSerializer tupleSerializer => tupleSerializer a) ->+ f a+ serializeArray ::+ (ToJSON a) =>+ f [a]++ -- | Give a name to a serializer.+ -- Should be globally unique, if possible.+ nameSerializer ::+ Text ->+ f a ->+ f a+ nameSerializer _ a = a+ -- | A class to provide the canonical way to encode a JSON. -- -- This class uses finally tagless style to keep the instructions for serializing abstract.@@ -109,11 +151,36 @@ -- -- This class is derivable generically, and will generate a \"nice\" format. -- In my opinion, at least.+--+-- If you want to customize this JSON, the newtype 'WithOptions' can be helpful, as it allows you to specify options for the generic serialization.+-- Unfortunately, due to a weird GHC quirk, you need to use it with @ -XStandaloneDeriving @ as well as @ -XDerivingVia @.+-- That is, you should write:+--+--+-- @+-- data PersonFilter = PersonFilter { filterFirstName :: Maybe Text, filterLastName :: Maybe Text }+-- deriving (Show, Read, Eq, Ord, Generic)+--+-- deriving via (WithOptions '[KeepNothingFields] PersonFilter) instance (ToJSON PersonFilter)+-- @++---- === __Laws__+--+-- This instance is lawless, unless 'Jordan.FromJSON.Class.FromJSON' is also defined for this type.+-- In that case, the representation serialized by 'ToJSON' should match that of the representation parsed by+-- 'Jordan.FromJSON.Class.FromJSON'. class ToJSON v where- toJSON :: (JSONSerializer f) => f v- default toJSON :: (Generic v, GToJSON (Rep v)) => (JSONSerializer f) => f v- toJSON = contramap from $ gToJSON defaultToJSONOptions+ toJSON :: (forall f. (JSONSerializer f) => f v)+ default toJSON :: (Generic v, GToJSON (Rep v), Typeable v) => (JSONSerializer f) => f v+ toJSON = contramap from $ gToJSON defaultToJSONOptions {toJSONBaseName = fq}+ where+ fq = T.unpack $ fullyQualifyName $ typeRep (Proxy :: Proxy v) +instance (Generic a, GToJSON (Rep a), Typeable a, SpecifiesToJSONOptions options) => ToJSON (WithOptions options a) where+ toJSON = contramap getWithOptions . contramap from $ gToJSON (specifiedToJSONOptions @options) {toJSONBaseName = fq}+ where+ fq = T.unpack $ fullyQualifyName $ typeRep (Proxy :: Proxy a)+ instance ToJSON () where toJSON = serializeNull @@ -155,13 +222,14 @@ toJSON = contramap T.pack serializeText instance forall a. (ToJSON a, Typeable a) => ToJSON (Ratio.Ratio a) where- toJSON = serializeObject objName $- divide divider (writeField "num" toJSON) (writeField "denom" toJSON)+ toJSON =+ serializeObject $+ divide divider (serializeField "num") (serializeField "denom") where- divider :: Ratio.Ratio a -> (a,a)- divider = (,) <$> Ratio.numerator <*> Ratio.denominator- objName = T.pack $ tyName <> ".Ratio"- tyName = (tyConModule <> const "." <> tyConName) $ typeRepTyCon $ typeRep (Proxy :: Proxy a)+ divider :: Ratio.Ratio a -> (a, a)+ divider = (,) <$> Ratio.numerator <*> Ratio.denominator+ objName = T.pack $ tyName <> ".Ratio"+ tyName = (tyConModule <> const "." <> tyConName) $ typeRepTyCon $ typeRep (Proxy :: Proxy a) instance (ToJSON a) => ToJSON (Semi.Min a) where toJSON = contramap Semi.getMin toJSON@@ -193,97 +261,166 @@ instance (ToJSON a) => ToJSON (Map.Map Text a) where toJSON = contramap Map.toList $ serializeDictionary toJSON -data ToJSONOptions- = ToJSONOptions- { toJSONEncodeSums :: SumTypeEncoding- , toJSONBaseName :: String- , toJSONRenderEnum :: String -> String+instance (ToJSON a) => ToJSON (Map.Map Integer a) where+ toJSON = contramap (fmap toTextKey . Map.toList) $ serializeDictionary toJSON+ where+ toTextKey (key, value) = (pack $ show key, value)++instance (ToJSON a) => ToJSON (NE.NonEmpty a) where+ toJSON = contramap NE.toList serializeArray++instance (ToJSON a) => ToJSON (Set.Set a) where+ toJSON = contramap Set.toList serializeArray++data ToJSONOptions = ToJSONOptions+ { toJSONEncodeSums :: SumTypeEncoding,+ toJSONBaseName :: String,+ toJSONRenderEnum :: String -> String,+ toJSONOmitNothingFields :: Bool } defaultToJSONOptions :: ToJSONOptions-defaultToJSONOptions- = ToJSONOptions TagInField "" id+defaultToJSONOptions =+ ToJSONOptions TagInField "" id True +class SpecifiesToJSONOptions (a :: [*]) where+ specifiedToJSONOptions :: ToJSONOptions++instance SpecifiesToJSONOptions '[] where+ specifiedToJSONOptions = defaultToJSONOptions++instance+ (SpecifiesToJSONOptions xs) =>+ SpecifiesToJSONOptions (OmitNothingFields ': xs)+ where+ specifiedToJSONOptions = (specifiedToJSONOptions @xs) {toJSONOmitNothingFields = True}++instance+ (SpecifiesToJSONOptions xs) =>+ SpecifiesToJSONOptions (KeepNothingFields ': xs)+ where+ specifiedToJSONOptions =+ (specifiedToJSONOptions @xs) {toJSONOmitNothingFields = False}+ class GToJSON v where gToJSON :: (JSONSerializer s) => ToJSONOptions -> s (v a) +-- | Top-level metadata is ignored. instance (ToJSON c) => GToJSON (K1 i c) where gToJSON _ = contramap (\(K1 a) -> a) toJSON +-- | Datatype metadata: we name the overall datatype with the baseName+-- passed in the options, then serialize the inner information. instance (GToJSON f, Datatype t) => GToJSON (D1 t f) where- gToJSON = contramap (\(M1 a) -> a) . gToJSON . addName+ gToJSON opts = nameSerializer (T.pack $ toJSONBaseName opts) $ contramap (\(M1 a) -> a) $ gToJSON opts++-- | Serialize out a no-argument constructor via a string value of its name.+-- This allows us to serialize out enum keys more easily.+--+-- This does not get a unique name as recursion cannot happen.+instance {-# OVERLAPS #-} (KnownSymbol name) => GToJSON (C1 (MetaCons name fixity 'False) U1) where+ gToJSON opts =+ serializeTextConstant (T.pack connNameS) where- addName b = b { toJSONBaseName = toJSONBaseName b <> dtname }- dtname = moduleName s <> "." <> datatypeName s- s :: D1 t f a- s = undefined+ connNameS = symbolVal (Proxy :: Proxy name) -instance {-# OVERLAPS #-} (Constructor t) => GToJSON (PartOfSum (C1 t U1)) where- gToJSON opts = contramap getPartOfSum $ serializeTextConstant enumValue+instance {-# OVERLAPS #-} (KnownSymbol name) => GToJSON (PartOfSum (C1 (MetaCons name fixity 'False) U1)) where+ gToJSON = contramap getPartOfSum . gToJSON++-- | IF we have a constructor with arguments, but not selectors, then+-- we serialize as a tuple.+instance {-# OVERLAPPABLE #-} (GToJSONTuple inner, Constructor (MetaCons n s 'False)) => GToJSON (C1 (MetaCons n s 'False) inner) where+ gToJSON opts =+ contramap (\(M1 a) -> a) $+ serializeTuple $ gToJSONTuple opts++-- | If we have a constructor with arguments AND selectors (IE, a record), then+-- we serialize out a JSON object.+instance {-# OVERLAPPABLE #-} (GToJSONObject inner, Constructor (MetaCons n s 'True)) => GToJSON (C1 (MetaCons n s 'True) inner) where+ gToJSON opts =+ contramap (\(M1 a) -> a) $+ serializeObject $+ gToJSONObject opts where- enumValue = T.pack $ toJSONRenderEnum opts $ conName (undefined :: C1 t U1 f)+ name = T.pack $ toJSONBaseName opts <> "." <> conName (undefined :: C1 (MetaCons n s 'True) inner a) +-- | If we have a single-argument constructor with no selectors, we want to just parse it directly.+instance {-# OVERLAPS #-} (ToJSON i) => GToJSON (C1 (MetaCons n s 'False) (S1 (MetaSel 'Nothing su ss ds) (Rec0 i))) where+ gToJSON _ = contramap (\(M1 (M1 (K1 s))) -> s) toJSON++-- | When rendering a sum type, and this is NOT an enum value, render via+-- the sum encoding option the user provided. instance {-# OVERLAPPABLE #-} (Constructor t, GToJSON (C1 t f)) => GToJSON (PartOfSum (C1 t f)) where gToJSON opts = contramap getPartOfSum encoded where encoded = case toJSONEncodeSums opts of TagVal -> tagged TagInField -> field- field = serializeObject objName $- writeField cn (gToJSON opts)- tagged = serializeObject objName $- contramap ((),) $- divided- (writeField "key" $ serializeTextConstant cn)- (writeField "value" $ gToJSON opts)+ field =+ serializeObject $+ serializeFieldWith cn (gToJSON opts)+ tagged =+ serializeObject $+ contramap ((),) $+ divided+ (serializeFieldWith "key" $ serializeTextConstant cn)+ (serializeFieldWith "value" $ gToJSON opts) objName = T.pack (toJSONBaseName opts) <> "." <> cn <> ".Output"- cn = T.pack $ conName (undefined :: C1 t f a)+ cn = T.pack $ conName (undefined :: C1 t f a) sumToEither :: (l :+: r) a -> Either (l a) (r a) sumToEither f = case f of L1 a -> Left a R1 a -> Right a +-- | If we can serialize out both sides of a sum-type, we can serialize out the sum type. instance forall l r. (GToJSON (PartOfSum l), GToJSON (PartOfSum r)) => GToJSON (l :+: r) where gToJSON :: forall f a. (JSONSerializer f) => ToJSONOptions -> f ((l :+: r) a) gToJSON opts = select sumToEither- (contramap PartOfSum $ gToJSON opts)- (contramap PartOfSum $ gToJSON opts)+ (contramap MkPartOfSum $ gToJSON opts)+ (contramap MkPartOfSum $ gToJSON opts) instance (GToJSON (PartOfSum l), GToJSON (PartOfSum r)) => GToJSON (PartOfSum (l :+: r)) where gToJSON opts = contramap getPartOfSum (gToJSON opts) -instance (GToJSON s) => GToJSON (S1 whatever s) where- gToJSON = contramap (\(M1 a) -> a) . gToJSON- instance GToJSON V1 where gToJSON _ = giveUp (error "how the hell did you construct a void data type?") +-- | Type class for generically converting to a JSON object.+-- We can do this if all the fields under a constructor are named. class GToJSONObject v where gToJSONObject :: (JSONObjectSerializer f) => ToJSONOptions -> f (v a) -instance (GToJSON f, Selector t) => GToJSONObject (S1 t f) where- gToJSONObject o- = contramap (\(M1 a) -> a)- $ writeField (T.pack $ selName v) (gToJSON o)- where- v :: M1 S t f a- v = undefined+instance {-# OVERLAPPABLE #-} (GToJSON f, KnownSymbol selector) => GToJSONObject (S1 (MetaSel (Just selector) su ss ds) f) where+ gToJSONObject o =+ contramap (\(M1 a) -> a) $+ serializeFieldWith (T.pack $ symbolVal (Proxy :: Proxy selector)) (gToJSON o) +instance {-# OVERLAPS #-} (ToJSON a, KnownSymbol selector) => GToJSONObject (S1 (MetaSel (Just selector) su ss ds) (Rec0 (Maybe a))) where+ gToJSONObject o = contramap map fieldWriter+ where+ fieldWriter+ | toJSONOmitNothingFields o = serializeJust name toJSON+ | otherwise = serializeFieldWith name toJSON+ map (M1 (K1 a)) = a+ name = T.pack $ symbolVal (Proxy :: Proxy selector)+ instance (GToJSONObject lhs, GToJSONObject rhs) => GToJSONObject (lhs :*: rhs) where gToJSONObject o = divide div (gToJSONObject o) (gToJSONObject o) where- div (a :*: b) = (a,b)+ div (a :*: b) = (a, b) -instance {-# OVERLAPPABLE #-} (GToJSONObject inner, Constructor t) => GToJSON (C1 t inner) where- gToJSON opts- = contramap (\(M1 a) -> a)- $ serializeObject name- $ gToJSONObject opts+class GToJSONTuple v where+ gToJSONTuple :: (JSONTupleSerializer f) => ToJSONOptions -> f (v a)++instance (GToJSONTuple lhs, GToJSONTuple rhs) => GToJSONTuple (lhs :*: rhs) where+ gToJSONTuple o = divide div (gToJSONTuple o) (gToJSONTuple o) where- name = T.pack $ toJSONBaseName opts <> "." <> conName (undefined :: C1 t inner a) <> ".Output"+ div (a :*: b) = (a, b) -instance {-# OVERLAPS #-} (ToJSON i) => GToJSON (C1 c (S1 (MetaSel 'Nothing su ss ds) (Rec0 i))) where- gToJSON _ = contramap (\(M1 (M1 (K1 s))) -> s) toJSON+instance (GToJSON f) => GToJSONTuple (S1 (MetaSel Nothing su ss ds) f) where+ gToJSONTuple o =+ contramap (\(M1 a) -> a) $+ serializeItemWith (gToJSON o)
− lib/Jordan/ToJSON/Text.hs
@@ -1,142 +0,0 @@-{-# LANGUAGE DerivingVia #-}-{-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-module Jordan.ToJSON.Text- where--import Data.Functor.Contravariant-import Data.Functor.Contravariant.Divisible-import Data.List (intersperse)-import qualified Data.Scientific as Sci-import Data.Semigroup (Endo(..))-import Data.String (IsString(..))-import qualified Data.Text as T-import Data.Void (absurd)-import Jordan.ToJSON.Class--data TextComma- = Written (T.Text -> T.Text)- | Empty--runWritten :: TextComma -> T.Text -> T.Text-runWritten Empty = id-runWritten (Written f) = f--instance IsString TextComma where- fromString s = Written (<> T.pack s)--instance Semigroup TextComma where- Empty <> a = a- a <> Empty = a- (Written f) <> (Written f') = Written (f . (", " <>) . f')--instance Monoid TextComma where- mempty = Empty--newtype CommaBuilder v = CommaBuilder { runCommaBuilder :: v -> TextComma }- deriving (Semigroup, Monoid) via (v -> TextComma)--runCommaBuilder' :: CommaBuilder v -> v -> T.Text -> T.Text-runCommaBuilder' (CommaBuilder f) = runWritten . f--instance Contravariant CommaBuilder where- contramap f (CommaBuilder v) = CommaBuilder (v . f)--instance Divisible CommaBuilder where- conquer = CommaBuilder (const Empty)- divide d (CommaBuilder b) (CommaBuilder c) = CommaBuilder $ \a ->- let (b', c') = d a in b b' <> c c'--newtype TextArray v = TextArray { runTextArray :: v -> ([T.Text] -> [T.Text]) }- deriving (Semigroup, Monoid) via (v -> Endo [T.Text])--instance Contravariant TextArray where- contramap f (TextArray b) = TextArray $ \a -> b (f a)--instance Divisible TextArray where- conquer = TextArray $ const mempty- divide d (TextArray b) (TextArray c) = TextArray $ \a ->- let (b', c') = d a in b b' . c c'--instance Decidable TextArray where- lose _ = TextArray $ const mempty- choose f (TextArray b) (TextArray c) = TextArray $ \a ->- case f a of- Left b' -> b b'- Right c' -> c c'--instance JSONTupleSerializer CommaBuilder where- writeItem f = CommaBuilder $ Written . runJSONText f--instance JSONObjectSerializer CommaBuilder where- writeField t s = CommaBuilder $ \arg ->- Written (quoteString t . (": " <> ) . runJSONText s arg)--instance JSONTupleSerializer TextArray where- writeItem f = TextArray $ \a -> ([runJSONText f a ""] <>)--newtype JSONText a- = JSONText { runJSONText :: a -> (T.Text -> T.Text) }- deriving (Semigroup, Monoid) via (a -> Endo T.Text)--instance Contravariant JSONText where- contramap f (JSONText s) = JSONText (s . f)--instance Selectable JSONText where- giveUp f = JSONText $ \a -> absurd (f a)- select f (JSONText lhs) (JSONText rhs) = JSONText $ either lhs rhs . f--convChar :: Char -> (T.Text -> T.Text)-convChar = \case- '\b' -> ("\\b" <>)- '\f' -> ("\\f" <>)- '\n' -> ("\\n" <>)- '\r' -> ("\\r" <>)- '\t' -> ("\\t" <>)- '"' -> ("\\\"" <>)- '\\' -> ("\\\\" <>)- o -> (T.singleton o <>)--isBadChar :: Char -> Bool-isBadChar = \case- '\b' -> True- '\f' -> True- '\r' -> True- '\t' -> True- '\\' -> True- '"' -> True- _ -> False--quoteString :: T.Text -> (T.Text -> T.Text)-quoteString t = ("\"" <>) . innerText . ("\"" <>)- where- innerText- | T.any isBadChar t = T.foldl' (\o a -> o . convChar a) id t- | otherwise = (t <>)--sArray :: (a -> T.Text -> T.Text) -> [a] -> (T.Text -> T.Text)-sArray _ [] = id-sArray f [x] = f x-sArray f (x : xs) = f x . ("," <>) . sArray f xs--instance JSONSerializer JSONText where- serializeNull = JSONText $ const ("null" <>)- serializeText = JSONText $ \a -> quoteString a- serializeTextConstant t = JSONText $ const $ quoteString t- serializeNumber = JSONText $ \n ->- ((T.pack $ Sci.formatScientific Sci.Generic Nothing n) <>)- serializeDictionary (JSONText serItem) = JSONText $ \n ->- ("{" <>) . keys n . ("}" <>)- where- keys v = runWritten $ foldMap (\(k, v) -> Written $ quoteString k . (": " <> ) . serItem v) v- serializeBool = JSONText $ \a -> ((if a then "true" else "false") <>)- serializeObject n obj = JSONText $ \arg ->- ("{" <>) . runCommaBuilder' obj arg . ("}" <>)- serializeTuple obj = JSONText $ \arg ->- ("[" <>) . runCommaBuilder' obj arg . ("}" <>)- serializeArray = JSONText $ \a -> ("[" <>) . sArray (runJSONText toJSON) a . (<> "]")--toJSONText :: (ToJSON a) => a -> T.Text-toJSONText a = runJSONText toJSON a ""
+ lib/Jordan/Types/Internal/AccumE.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE PatternSynonyms #-}++-- | Either, but with an Applicative instance that combines errors via '<>'.+--+-- This is sometimes known as the validation Applicative.+-- There are Haskell packages providing this type, however, in the interest of minimized+-- dependency footprint we use this.+module Jordan.Types.Internal.AccumE+ ( AccumE+ ( AccumE,+ getAccumE,+ AccumEL,+ AccumER+ ),+ )+where++import Control.Applicative+import Data.Bifunctor+import GHC.Generics+import Text.Read++-- | A version of Either that accumulates errors via an instance of 'Semigroup'.+--+-- This is sometimes called the validation applicative.+newtype AccumE err val = AccumE {getAccumE :: Either err val}+ deriving (Functor) via Either err+ deriving (Bifunctor) via Either+ deriving (Generic)++-- | Show instance uses the 'AccumER' and 'AccumEL' pattern synonyms.+instance (Show a, Show b) => Show (AccumE a b) where+ showsPrec prec = \case+ AccumEL l ->+ showParen (prec > 10) $+ showString "AccumEL " . showsPrec 11 l+ AccumER r ->+ showParen (prec > 10) $+ showString "AccumER " . showsPrec 11 r++-- | Read instance uses the 'AccumER' and 'AccumEL' pattern synonyms.+instance (Read a, Read b) => Read (AccumE a b) where+ readPrec = parens $ left +++ right+ where+ left = do+ Ident "AccumEL" <- lexP+ AccumEL <$> step readPrec+ right = do+ Ident "AccumER" <- lexP+ AccumER <$> step readPrec++-- | Construct an error value.+pattern AccumEL :: err -> AccumE err val+pattern AccumEL l = AccumE (Left l)++-- | Construct a good value.+-- Equivalent to 'pure'.+pattern AccumER :: val -> AccumE err val+pattern AccumER r = AccumE (Right r)++{-# COMPLETE AccumEL, AccumER #-}++-- | Applicative accumulates errors.+--+-- Note that this is *strict* in the error, because this+-- can sometimes reduce the number of allocations in the places+-- where we use this.+instance (Semigroup e) => Applicative (AccumE e) where+ pure !a = AccumE (Right a)+ {-# INLINE pure #-}+ (AccumE !f) <*> (AccumE !a) = AccumE $ case f of+ Left !e -> case a of+ Left !e' -> Left $ e <> e'+ Right !a' -> Left e+ Right !fab -> case a of+ Left !e -> Left e+ Right !arg -> Right $ fab arg+ {-# INLINE (<*>) #-}+ liftA2 f (AccumE arg) (AccumE arg') =+ AccumE+ ( case arg of+ Left e -> case arg' of+ Left e' -> Left $ e <> e'+ Right b -> Left e+ Right a -> case arg' of+ Left e -> Left e+ Right b -> Right (f a b)+ )+ {-# INLINE liftA2 #-}++-- | Alternative takes the first result if there is a result.+-- If there is not, will *not* accumulate errors.+instance (Monoid m) => Alternative (AccumE m) where+ empty = AccumEL mempty+ (AccumER a) <|> _ = AccumER a+ (AccumEL _) <|> (AccumER a) = AccumER a+ (AccumEL a) <|> (AccumEL _) = AccumEL a+ {-# INLINE (<|>) #-}++-- | Semigroup accumulates errors if both are errors, otherwise+-- it returns the first good value.+instance (Semigroup e) => Semigroup (AccumE e a) where+ (AccumE lhs) <> (AccumE rhs) = AccumE $ case lhs of+ Left e -> case rhs of+ Left e' -> Left $ e <> e'+ Right a -> Right a+ Right a -> Right a++-- mempty is an error with 'mempty'+instance (Monoid err) => Monoid (AccumE err a) where+ mempty = AccumEL mempty
+ lib/Jordan/Types/Internal/MergeMap.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE UndecidableInstances #-}++-- | Provides a MergeMap, which is basically a map with+module Jordan.Types.Internal.MergeMap where++import qualified Data.Map as Map+import GHC.Exts (IsList (..))++-- | A map where '<>' merges values with '<>'.+newtype MergeMap key val = MergeMap {getMergeMap :: Map.Map key val}+ deriving (Eq)+ deriving (IsList) via (Map.Map key val)+ deriving (Functor, Foldable) via (Map.Map key)++instance Traversable (MergeMap key) where+ traverse f (MergeMap m) = MergeMap <$> traverse f m++instance (Semigroup val, Ord key) => Semigroup (MergeMap key val) where+ (MergeMap lhs) <> (MergeMap rhs) = MergeMap $ Map.unionWith (<>) lhs rhs+ {-# INLINE (<>) #-}++instance (Semigroup val, Ord key) => Monoid (MergeMap key val) where+ mempty = MergeMap mempty+ {-# INLINE mempty #-}++mergeSingleton :: k -> v -> MergeMap k v+mergeSingleton k v = MergeMap (Map.singleton k v)
+ lib/Jordan/Types/JSONError.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE UndecidableInstances #-}++module Jordan.Types.JSONError where++import Control.DeepSeq+import Data.Coerce+import Data.Foldable+import Data.Functor.Contravariant+import qualified Data.List.NonEmpty as NE+import qualified Data.Map.Strict as Map+import qualified Data.Set as Set+import Data.Text (Text, pack)+import qualified Data.Text.Lazy.Builder as LB+import GHC.Exts (IsList (..))+import GHC.Generics+import Jordan.FromJSON.Class+import Jordan.ToJSON.Class+import Jordan.Types.Internal.MergeMap+import Jordan.Types.JSONType++data JSONError+ = -- | Generic, user-provided error message+ ErrorMesage Text+ | -- | JSON was not up to spec+ ErrorInvalidJSON+ | -- | Bad type encountered (Expected, Actual)+ ErrorBadType {expectedType :: !JSONType, actualType :: !JSONType}+ | -- | There was no value for this JSON+ ErrorNoValue+ | -- | Text constant was wrong.+ ErrorBadTextConstant {expectedText :: !Text, actualText :: !Text}+ | -- | An object had some bad values.+ ErrorBadObject JSONObjectError+ | -- | An array had some bad indices+ ErrorBadArray JSONArrayError+ | -- | One of multiple possible errors.+ ErrorChoice !(Set.Set JSONError)+ deriving (Show, Eq, Read, Ord, Generic)+ deriving anyclass (ToJSON, FromJSON)++instance Semigroup JSONError where+ (<>) ErrorNoValue ErrorNoValue = ErrorNoValue+ (<>) ErrorNoValue !a = a+ (<>) !a ErrorNoValue = a+ (<>) (ErrorChoice lhs) (ErrorChoice rhs) = ErrorChoice $ lhs <> rhs+ (<>) (ErrorChoice a) !rhs = ErrorChoice $ a <> Set.singleton rhs+ (<>) !a (ErrorChoice rhs) = ErrorChoice (Set.singleton a <> rhs)+ (<>) !lhs !rhs = ErrorChoice (Set.singleton lhs <> Set.singleton rhs)++instance NFData JSONError++-- | 'mempty' is 'ErrorNoValue'+instance Monoid JSONError where+ mempty = ErrorNoValue++newtype JSONObjectError = MkJSONObjectError (Map.Map Text JSONError)+ deriving (Eq, Ord, Generic)+ deriving (NFData, IsList, Show, Read) via (Map.Map Text JSONError)+ deriving (Semigroup, Monoid) via (MergeMap Text JSONError)++instance FromJSON JSONObjectError where+ fromJSON = MkJSONObjectError <$> fromJSON++instance ToJSON JSONObjectError where+ toJSON = contramap (Map.toAscList . keyValueErrors) $ serializeDictionary toJSON++singleObjectError :: Text -> JSONError -> JSONObjectError+singleObjectError t = MkJSONObjectError . Map.singleton t++keyValueErrors :: JSONObjectError -> Map.Map Text JSONError+keyValueErrors = coerce++newtype JSONArrayError = MkJSONArrayError (Map.Map Integer JSONError)+ deriving (Eq, Ord, Generic)+ deriving (NFData, IsList, Show, Read) via (Map.Map Integer JSONError)+ deriving (Semigroup, Monoid) via (MergeMap Integer JSONError)++indexErrors :: JSONArrayError -> Map.Map Integer JSONError+indexErrors = coerce++instance FromJSON JSONArrayError where+ fromJSON = MkJSONArrayError <$> fromJSON++instance ToJSON JSONArrayError where+ toJSON = contramap indexErrors toJSON++prettyPrintJSONError :: JSONError -> Text+prettyPrintJSONError = go id+ where+ go :: (Text -> Text) -> JSONError -> Text+ go mapper = \case+ ErrorMesage txt -> mapper txt+ ErrorInvalidJSON -> mapper "Invalid JSON"+ ErrorBadType jt jt' -> mapper "Bad type: Expected " <> pack (show jt) <> " got " <> pack (show jt')+ ErrorNoValue -> mapper "Expected to receive a value, but did not"+ ErrorBadTextConstant txt txt' -> mapper "Bad text constant: Expected \"" <> txt <> "\", got \"" <> txt' <> "\""+ ErrorBadObject (MkJSONObjectError map) -> Map.foldlWithKey (\acc key value -> acc <> "\n" <> mapper key <> ":\n" <> go (mapper . (" " <>)) value) mempty map+ ErrorBadArray (MkJSONArrayError map) -> Map.foldlWithKey (\acc key value -> acc <> "\n" <> mapper (pack $ show key) <> ":\n" <> go (mapper . (" " <>)) value) mempty map+ ErrorChoice ne -> mapper "One of:\n" <> foldl' (\acc err -> acc <> go newMap err) mempty ne+ where+ newMap = mapper . (" " <>)
+ lib/Jordan/Types/JSONType.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE DeriveGeneric #-}++module Jordan.Types.JSONType where++import Control.DeepSeq+import GHC.Generics+import Jordan.FromJSON.Class (FromJSON)+import Jordan.ToJSON.Class (ToJSON)++data JSONType+ = JSONTypeNull+ | JSONTypeBool+ | JSONTypeText+ | JSONTypeNumber+ | JSONTypeArray+ | JSONTypeObject+ deriving (Show, Eq, Read, Ord, Bounded, Enum, Generic)++instance ToJSON JSONType++instance FromJSON JSONType++instance NFData JSONType
+ lib/Jordan/Types/JSONValue.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-}+module Jordan.Types.JSONValue+ ( JSONValue (..)+ ) where++import Data.Functor (($>))+import Data.Functor.Contravariant (Contravariant(..))+import qualified Data.Map.Strict as Map+import Data.Scientific (Scientific)+import Data.Text (Text)+import GHC.Generics (Generic(..))+import Jordan.FromJSON.Class (FromJSON(..), JSONParser(..))+import Jordan.ToJSON.Class (JSONSerializer(..), Selectable(..), ToJSON(..), selected)++-- | A type for any JSON value.+-- This is a basic Haskell sum type representation.+--+-- This is intended to for use when working with JSON where you do not know much about its structure.+data JSONValue+ = JNull+ | JBool Bool+ | JText Text+ | JNumber Scientific+ | JArray [JSONValue]+ | JObject (Map.Map Text JSONValue)+ deriving (Show, Eq, Ord, Generic)++instance FromJSON JSONValue where+ fromJSON+ = (parseNull $> JNull)+ <> (JText <$> parseText)+ <> (JBool <$> parseBool)+ <> (JNumber <$> parseNumber)+ <> nameParser "Jordan.JSONValue.Array.Input" (JArray <$> parseArrayWith fromJSON)+ <> nameParser "Jordan.JSONValue.Map.Input" (JObject . Map.fromList <$> parseDictionary fromJSON)++type AsEither+ = Either ()+ (Either Bool+ (Either Text+ (Either Scientific+ (Either [JSONValue] (Map.Map Text JSONValue)))))++toNestedEither+ :: JSONValue+ -> AsEither+toNestedEither = \case+ JNull -> Left ()+ JBool b -> Right (Left b)+ JText txt -> Right (Right (Left txt))+ JNumber sci -> Right (Right (Right (Left sci)))+ JArray jvs -> Right (Right (Right (Right (Left jvs))))+ JObject map -> Right (Right (Right (Right (Right map))))++instance ToJSON JSONValue where+ toJSON = select toNestedEither serializeNull s1+ where+ s1 = selected serializeBool s2+ s2 = selected serializeText s3+ s3 = selected serializeNumber s4+ s4+ = selected (nameSerializer "Jordan.JSONValue.Array.Output" serializeArray)+ $ nameSerializer "Jordan.JSONValue.Map.Output"+ $ contramap Map.toList+ $ serializeDictionary toJSON
− test/Jordan/FromJSON/MegaparsecSpec.hs
@@ -1,59 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeApplications #-}-module Jordan.FromJSON.MegaparsecSpec- ( spec- ) where--import Data.Text (Text)-import Jordan.FromJSON.Class-import Jordan.FromJSON.Megaparsec-import Jordan.SpecDefs-import Test.Hspec (Spec, describe, it)-import Test.Hspec.Megaparsec-import Text.Megaparsec (parse)--parse' a = parse a ""--parseJ :: (FromJSON a) => Text -> Either ParseError a-parseJ = parse' megaparsecParser--spec :: Spec-spec = describe "megaparsec parsing" $ do- commaSpec- anyFieldSpec- jsonTextSpec- basicParsingSpec (\t v -> parseJ t `shouldParse` v)- specialCaseSpec--commaSpec :: Spec-commaSpec = describe "comma parser" $ do- it "parses just a comma" $- parse' comma `shouldSucceedOn` ","- it "parses a comma with some extra whitespace" $- parse' comma `shouldSucceedOn` ", \n\n"--anyFieldSpec :: Spec-anyFieldSpec = describe "parseAnyField" $ do- it "parses a very basic null field" $- parse' parseAnyField `shouldSucceedOn` "\"foo\": null"- it "parses an overly whitespaced field" $- parse' parseAnyField `shouldSucceedOn` "\"foo\" : \n\n\n null"--jsonTextSpec :: Spec-jsonTextSpec = describe "parseJSONText" $ do- let parseText = parse' parseJSONText- it "parses a super basic string" $ do- parseText `shouldSucceedOn` "\"foo\""- it "parses with an escaped backslash" $ do- parseText "\"foo\\\\\"" `shouldParse` "foo\\"- it "parses with an escaped quote" $ do- parseText "\"foo\\\"\"" `shouldParse` "foo\""- it "parses with unicode" $ do- parseText "\"foo\\u2795\"" `shouldParse` "foo➕"- it "parses with trailing whitespace" $ do- parseText "\"foo\" " `shouldParse` "foo"--specialCaseSpec :: Spec-specialCaseSpec = describe "special cases" $ do- it "does not allow mismatched labels" $- parse (megaparsecParser @GenericSum) "" `shouldFailOn` mismatchType
+ test/Jordan/FromJSON/UnboxedReportingSpec.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE LambdaCase #-}++module Jordan.FromJSON.UnboxedReportingSpec+ ( spec,+ prettyWhenError,+ )+where++import Data.Text (Text)+import Data.Text.Encoding (encodeUtf8)+import Jordan+import Jordan.FromJSON.Class (FromJSON (fromJSON))+import Jordan.SpecDefs (basicParsingSpec)+import Jordan.Types.JSONError+import Test.Hspec (Spec, describe, it, shouldBe)++prettyWhenError :: Either JSONError a -> Either Text a+prettyWhenError = \case+ Left je -> Left $ prettyPrintJSONError je+ Right a -> Right a++spec :: Spec+spec = describe "Jordan.FromJSON.Reporting" $ do+ basicParsingSpec $ \t v ->+ prettyWhenError (parseOrReport (encodeUtf8 t)) `shouldBe` Right v
test/Jordan/RoundTripSpec.hs view
@@ -1,56 +1,80 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}-module Jordan.RoundTripSpec- where -import Data.ByteString.Lazy (fromStrict, toStrict)-import Data.Functor.Contravariant (Contravariant(..))-import Data.Proxy (Proxy(..))+module Jordan.RoundTripSpec where++import Data.ByteString.Lazy (ByteString, fromStrict, toStrict)+import qualified Data.ByteString.Lazy as LBS+import Data.Functor.Contravariant (Contravariant (..)) import Data.Text (Text, unpack)+import qualified Data.Text as Text import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Data.Typeable (Proxy (..), Typeable) import GHC.Generics-import Jordan (parseViaAttoparsec, parseViaMegaparsec, toJSONText, toJSONViaBuilder)-import Jordan.FromJSON.Class (FromJSON(..), JSONParser(..))-import Jordan.ToJSON.Class (JSONSerializer(..), ToJSON(..))+import Jordan (parseViaAttoparsec, toJSONViaBuilder)+import Jordan.FromJSON.Class (FromJSON (..), GFromJSON (..), JSONParser (..))+import Jordan.FromJSON.UnboxedReporting (parseOrReport)+import Jordan.Generic.Options+import Jordan.ToJSON.Class (GToJSON (..), JSONSerializer (..), ToJSON (..)) import Test.Hspec (Spec, describe) import Test.Hspec.QuickCheck (prop) import Test.QuickCheck import Test.QuickCheck.Utf8 -makeResult- :: (ToJSON a, FromJSON a, Arbitrary a, Show a, Eq a)- => Proxy a- -> (a -> b)- -> (b -> String)- -> (b -> Either String a)- -> Property+data PropertyResult exp = MkPropertyResult+ { expectedValue :: exp,+ actualValue :: Either String exp,+ jsonStringTested :: Text+ }+ deriving (Show, Read)++roundtripProperty ::+ (ToJSON a, FromJSON a, Arbitrary a, Show a, Eq a, Eq err, Show err) =>+ Proxy a ->+ (ByteString -> Either err a) ->+ (err -> String) ->+ Property+roundtripProperty (Proxy :: Proxy a) parser mapErr =+ forAllShrink (arbitrary @a) (shrink @a) $ \a ->+ let built = toJSONViaBuilder a+ in counterexample (show built) $ parser (toJSONViaBuilder a) === Right a++makeResult ::+ (ToJSON a, FromJSON a, Arbitrary a, Show a, Eq a) =>+ Proxy a ->+ (a -> b) ->+ (b -> String) ->+ (b -> Either String a) ->+ Property makeResult (Proxy :: Proxy a) convForward convString convBack = forAllShow (arbitrary @a) showResult convert- where- showResult a- = show a- <> "\n"- <> convString (convForward a)- <> "\n"- <> showError (convBack $ convForward a)- convert a = convBack (convForward a) == pure a- showError :: Either String a -> String- showError (Left err) = "Error\n" <> err- showError (Right a) = "Success: " <> show a+ where+ showResult a =+ show a+ <> "\n"+ <> convString (convForward a)+ <> "\n"+ <> showError (convBack $ convForward a)+ convert a = convBack (convForward a) == pure a+ showError :: Either String a -> String+ showError (Left err) = "Error\n" <> err+ showError (Right a) = "Success: " <> show a -newtype ExtremelyBasic- = ExtremelyBasic { getExtremelyBasic :: () }+newtype ExtremelyBasic = ExtremelyBasic {getExtremelyBasic :: ()} deriving (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) instance Arbitrary ExtremelyBasic where arbitrary = ExtremelyBasic <$> arbitrary -data TwoFieldsRec- = TwoFieldsRec { firstField :: Int, secondField :: Int }+data TwoFieldsRec = TwoFieldsRec {firstField :: Int, secondField :: Int} deriving (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) @@ -69,37 +93,43 @@ instance Arbitrary RLMSum where arbitrary = arbitraryBoundedEnum -data ManyChoices- = ChoseFirst { getFirst :: Int }- | ChoseSecond { getSecondA :: Int, getSecondB :: Int }+data TwoChoices+ = ChoseFirst {getFirst :: Int}+ | ChoseSecond {getSecondA :: Int, getSecondB :: Int} deriving (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) -instance Arbitrary ManyChoices where- arbitrary- = oneof- [ ChoseFirst <$> arbitrary- , ChoseSecond <$> arbitrary <*> arbitrary- ]+instance Arbitrary TwoChoices where+ arbitrary =+ oneof+ [ ChoseFirst <$> arbitrary,+ ChoseSecond <$> arbitrary <*> arbitrary+ ] -data FakePerson- = FakePerson- { age :: Int- , name :: String- , cool :: Bool- } deriving (Eq, Show, Generic)+data FakePerson = FakePerson+ { age :: Int,+ name :: String,+ cool :: Bool+ }+ deriving (Eq, Show, Generic) deriving anyclass (FromJSON, ToJSON) instance Arbitrary FakePerson where- arbitrary- = FakePerson- <$> arbitrary- <*> fmap unpack genValidUtf8- <*> arbitrary+ arbitrary =+ FakePerson+ <$> arbitrary+ <*> fmap unpack genValidUtf8+ <*> arbitrary -newtype OnlyText = OnlyText { getText :: Text }- deriving (Show, Eq)+newtype OnlyText = OnlyText {getText :: Text}+ deriving (Show, Eq, Generic) +shrinkText :: Text -> [Text]+shrinkText t+ | Text.length t == 0 = []+ | Text.length t == 1 = [""]+ | otherwise = Text.singleton <$> Text.unpack t+ instance ToJSON OnlyText where toJSON = contramap getText serializeText @@ -108,70 +138,139 @@ instance Arbitrary OnlyText where arbitrary = OnlyText <$> genValidUtf8+ shrink (OnlyText t) = OnlyText <$> shrinkText t data EnumyObject = EnumA | EnumB | EnumC- | EnumObject { enumValue :: Text }+ | EnumObject {enumValue :: Text}+ | EnumOther OnlyText deriving (Show, Eq, Generic) deriving anyclass (ToJSON, FromJSON) instance Arbitrary EnumyObject where- arbitrary- = oneof- [ pure EnumA- , pure EnumB- , pure EnumC- , EnumObject <$> genValidUtf8- ]+ arbitrary =+ oneof+ [ pure EnumA,+ pure EnumB,+ pure EnumC,+ EnumObject <$> genValidUtf8,+ EnumOther <$> arbitrary+ ]+ shrink (EnumObject o) = EnumObject <$> shrinkText o+ shrink (EnumOther ot) = EnumOther <$> shrink ot+ shrink _ = [] -data AllTogether- = AllTogether- { extremelyBasic :: ExtremelyBasic- , twoFieldsRec :: TwoFieldsRec- , rlmSum :: RLMSum- , fakePerson :: FakePerson- , onlyText :: OnlyText- , enumyObject :: EnumyObject- } deriving (Eq, Show, Generic)+data AllTogether = AllTogether+ { extremelyBasic :: ExtremelyBasic,+ twoFieldsRec :: TwoFieldsRec,+ rlmSum :: RLMSum,+ fakePerson :: FakePerson,+ onlyText :: OnlyText,+ enumyObject :: EnumyObject+ }+ deriving (Eq, Show, Generic) deriving anyclass (ToJSON, FromJSON) instance Arbitrary AllTogether where- arbitrary- = AllTogether- <$> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary- <*> arbitrary+ arbitrary =+ AllTogether+ <$> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ <*> arbitrary+ shrink (AllTogether ub tfr rs fp ot eo) =+ AllTogether+ <$> shrink ub+ <*> shrink tfr+ <*> shrink rs+ <*> shrink fp+ <*> shrink ot+ <*> shrink eo +data AnnoyinglyOptional = AnnoyinglyOptional+ { annoyingFirst :: Maybe Int,+ annoyingSecond :: Maybe Int,+ annoyingThird :: Maybe Int+ }+ deriving (Show, Read, Eq, Ord, Generic)+ deriving anyclass (ToJSON, FromJSON)++instance Arbitrary AnnoyinglyOptional where+ arbitrary =+ pure $ AnnoyinglyOptional Nothing Nothing Nothing++-- <$> arbitrary <*> arbitrary <*> arbitrary++data JustNulls = JustNulls+ { jna :: (),+ jnb :: (),+ jnc :: (),+ jnd :: (),+ jne :: (),+ jnf :: ()+ }+ deriving (Show, Read, Eq, Ord, Generic)+ deriving anyclass (ToJSON, FromJSON)++instance Arbitrary JustNulls where+ arbitrary =+ pure $+ JustNulls () () () () () ()+ showViaText :: (ToJSON a) => a -> String-showViaText = unpack . toJSONText+showViaText = unpack . decodeUtf8 . LBS.toStrict . toJSONViaBuilder showViaBuilder :: (ToJSON a) => a -> String showViaBuilder = unpack . decodeUtf8 . toStrict . toJSONViaBuilder -roundtrips- :: (Arbitrary a, Show a, Eq a, ToJSON a, FromJSON a)- => String- -> Proxy a- -> Spec-roundtrips n p = describe ("round-trippping " <> n) $ do- describe "when serializing via text" $ do- let q = makeResult p toJSONText unpack- prop "roundtrips back via megaparsec" $- q parseViaMegaparsec- prop "roundtrips back via attoparsec" $- q (parseViaAttoparsec . encodeUtf8)+showLeft :: (Show a) => Either a b -> Either [Char] b+showLeft = \case+ Left a -> Left (show a)+ Right b -> Right b++instance Arbitrary a => Arbitrary (WithOptions opts a) where+ arbitrary = WithOptions <$> arbitrary+ shrink (WithOptions a) = WithOptions <$> shrink a++roundtrips' ::+ (Arbitrary a, Show a, Eq a, ToJSON a, FromJSON a) =>+ Proxy a ->+ Spec+roundtrips' p = do describe "when serializing via a builder" $ do- let q = makeResult p toJSONViaBuilder (unpack . decodeUtf8 . toStrict)- prop "roundtrips back via megaparsec" $- q (parseViaMegaparsec . decodeUtf8 . toStrict) prop "roundtrips back via attoparsec" $- q (parseViaAttoparsec . toStrict)+ roundtripProperty p (parseViaAttoparsec . toStrict) show+ prop "roundstrips back via unboxed reporting" $+ roundtripProperty p (parseOrReport . toStrict) show +roundtrips ::+ ( Arbitrary a,+ Typeable a,+ Show a,+ Eq a,+ ToJSON a,+ FromJSON a,+ Generic a,+ GToJSON (Rep a),+ GFromJSON (Rep a)+ ) =>+ String ->+ Proxy a ->+ Spec+roundtrips n p@(Proxy :: Proxy a) = describe ("round-tripping " <> n) $ do+ describe "default roundtrips" $+ roundtrips' p+ describe "roundtrips with no options" $+ roundtrips' (Proxy @(WithOptions '[] a))+ describe "Roundtrips omitting nothing fields" $+ roundtrips' (Proxy @(WithOptions '[OmitNothingFields] a))+ describe "Roundtrips keeping nothing fields" $+ roundtrips' (Proxy @(WithOptions '[KeepNothingFields] a))+ spec :: Spec spec = describe "round-tripping generic values" $ do roundtrips "a newtype around ()" (Proxy @ExtremelyBasic)@@ -181,3 +280,5 @@ roundtrips "just text" (Proxy @OnlyText) roundtrips "an object with some constructors as enums" (Proxy @EnumyObject) roundtrips "an object composed of all rountrip'd objects" (Proxy @AllTogether)+ roundtrips "an object with optional fields" (Proxy @AnnoyinglyOptional)+ roundtrips "an object that is jut a bunch of nulls" (Proxy @JustNulls)
test/Jordan/SpecDefs.hs view
@@ -1,23 +1,27 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingVia #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RankNTypes #-}-module Jordan.SpecDefs- where -import Control.Applicative (Alternative((<|>)))+module Jordan.SpecDefs where++import Control.Applicative (Alternative ((<|>)))+import Data.Foldable import Data.Functor.Contravariant import Data.Functor.Contravariant.Divisible-import Data.String (IsString(..))+import Data.Scientific (Scientific, scientific)+import Data.String (IsString (..)) import Data.Text (Text, pack) import GHC.Generics (Generic) import Jordan.FromJSON.Class+import Jordan.Generic.Options import Jordan.ToJSON.Class-import Test.Hspec (Arg, Example, Spec, SpecWith, describe, it, shouldBe)+import Test.Hspec (Arg, Example, Spec, SpecWith, describe, fit, it, shouldBe) import Text.RawString.QQ -newtype BasicStruct- = BasicStruct { bar :: () }+newtype BasicStruct = BasicStruct {bar :: ()} deriving (Show, Eq, Ord) goodBasic :: BasicStruct@@ -26,48 +30,62 @@ instance FromJSON BasicStruct where fromJSON = (BasicStruct <$> fromNull) <> fromObject where- fromObject = parseObject "TestItems.BasicStruct.Output" $- BasicStruct <$> parseField "bar"+ fromObject =+ parseObject $+ BasicStruct <$> parseField "bar" fromNull = parseNull instance ToJSON BasicStruct where- toJSON = serializeObject "TestItems.BasicStruct.Input" $ writeField "bar" serializeNull+ toJSON = serializeObject $ serializeFieldWith "bar" serializeNull basicNull = pack [r| null |]++basicOneFieldNoSpace = pack [r|{"bar":null}|]+ basicOneField = pack [r| { "bar": null } |]-basicEscaped = pack [r| { "\u0062\u0061\u0072": null } |]++basicEscaped = pack [r| { "\u0062\u0061\u0072": null }|]++basicEscapedNoSpace = pack [r|{"\u0062\u0061\u0072":null}|]+ basicPartialEscape = pack [r| { "\u0062a\u0072": null } |] basicExtraFields = pack [r| { "whatever": null, "bar": null, "baz": null } |] basicArray = pack [r| [null, { "whatever": null, "bar": null }, null, null] |] -data TwoFields- = TwoFields { one :: (), two :: () }+data TwoFields = TwoFields {one :: (), two :: ()} deriving (Show, Eq, Ord, Generic) goodTwo :: TwoFields goodTwo = TwoFields () () instance FromJSON TwoFields where- fromJSON = parseObject "TwoFields" $ TwoFields <$> parseField "one" <*> parseField "two"+ fromJSON = parseObject $ TwoFields <$> parseField "one" <*> parseField "two" -twoSimple = pack [r| { "one": null, "two": null } |]+twoSimple = pack [r| { "one" : null, "two": null } |] -twoScramble = pack [r| { "two": null, "one": null } |]+twoScramble = pack [r| {"two":null,"one":null} |] -twoExtra = pack [r|+twoExtra =+ pack+ [r| { "ignore": [], "one": null,- "bad": {},- "another": [1,2,3],- "yetAgain": null,+ "four": [],+ "five": "test",+ "seven": {},+ "six": "why tho", "two": null,- "three": null }+ "three": {},+ "ignored": [1,2,3,4, { " foo " : null }]+ } |] -twoScrambleExtra = pack [r|+twoScrambleExtra =+ pack+ [r| { "ignore": null, "bad": null,@@ -78,11 +96,11 @@ } |] -data GenericStruct- = GenericStruct- { firstLabel :: ()- , secondLabel :: [()]- } deriving (Show, Read, Eq, Ord, Generic)+data GenericStruct = GenericStruct+ { firstLabel :: (),+ secondLabel :: [()]+ }+ deriving (Show, Read, Eq, Ord, Generic) instance FromJSON GenericStruct @@ -96,14 +114,57 @@ instance FromJSON GenericSum sumBasic = pack [r| { "GenericBasic": null } |]+ sumBasicObj = pack [r| { "GenericBasic": { "bar": null } } |] sumTwo = pack [r| { "GenericTwo": { "one": null, "two": null, "bar": null } } |]+ mismatchType = pack [r| { "GenericTwo": null } |] -basicWritingSpec- :: (forall val. (ToJSON val, Show val) => val -> Text)- -> Spec+data Coord = Coord {x :: !Double, y :: !Double}+ deriving (Show, Eq, Ord, Generic)++instance FromJSON Coord where+ fromJSON = fromObject <> fromArray+ where+ fromObject = parseObject $ Coord <$> parseField "x" <*> parseField "y"+ fromArray = parseTuple $ Coord <$> consumeItem <*> consumeItem++coordBasic = pack [r|{ "x": 10, "y": 10 } |]++coordReverse = pack [r| { "y": 11, "x": 12 } |]++coordExtra = pack [r| { "x": 1, "y": 2, "z": 3 } |]++coordNoSpaceDecimal = pack [r|{"x":0.0,"y":0.0}|]++coordTuple = pack [r| [1, 20] |]++data HomogenousCoord = HomogenousCoord {hx :: !Double, hy :: !Double, hz :: !Double}+ deriving (Show, Read, Eq, Ord, Generic)++instance FromJSON HomogenousCoord where+ fromJSON = fromObject <> fromObjectZero <> fromArray <> fromArrayZero+ where+ fromObject = parseObject $ mk <$> parseField "x" <*> parseField "y" <*> parseField "z"+ fromObjectZero = parseObject $ mk <$> parseField "x" <*> parseField "y" <*> pure 1.0+ fromArray = parseTuple $ mk <$> consumeItem <*> consumeItem <*> consumeItem+ fromArrayZero = parseTuple $ mk <$> consumeItem <*> consumeItem <*> pure 1.0+ mk = HomogenousCoord++data WeirdFeedback+ = GeneralFeedback Text+ | SpecificFeedback [(Text, Text)]+ deriving (Show, Eq)++instance FromJSON WeirdFeedback where+ fromJSON =+ parseObject (GeneralFeedback <$> parseField "general")+ <> (SpecificFeedback <$> parseDictionary fromJSON)++basicWritingSpec ::+ (forall val. (ToJSON val, Show val) => val -> Text) ->+ Spec basicWritingSpec writeJSON = do describe "writing basic primitives" $ do it "writes nulls" $@@ -117,7 +178,7 @@ it "writes an array with one item" $ do writeJSON [()] `shouldBe` "[null]" it "writes an array with two items" $ do- writeJSON [(),()] `shouldBe` "[null,null]"+ writeJSON [(), ()] `shouldBe` "[null,null]" it "writes the number 1" $ do writeJSON (1 :: Int) `shouldBe` "1.0" it "writes the number 1.5" $ do@@ -134,63 +195,70 @@ it "writes properly" $ do writeJSON goodBasic `shouldBe` [r|{"bar": null}|] -basicParsingSpec- :: (Example a)- => (forall val. (FromJSON val, Show val, Eq val) => Text -> val -> a)- -> SpecWith (Arg a)+basicParsingSpec ::+ (Example a) =>+ (forall val. (FromJSON val, Show val, Eq val) => Text -> val -> a) ->+ SpecWith (Arg a) basicParsingSpec parseMatch = do+ let parseExample t v = it ("parses " <> show t <> " to " <> show v) $ t `parseMatch` v+ describe "parsing basic structure" $ do- it "parses true" $- "true" `parseMatch` True- it "parses false" $- "false" `parseMatch` False- it "parses a raw null" $- "null" `parseMatch` ()- it "parses an empty array of nulls" $- "[]" `parseMatch` ([] :: [()])- it "parses a one-null array" $ do- "[null ]" `parseMatch` [()]- it "parses a two-null array with weird spaces" $ do- "[null\n,null]" `parseMatch` [(), ()]- it "parses null to a basic struct" $- basicNull `parseMatch` goodBasic- it "parses with field" $- basicOneField `parseMatch` goodBasic- it "parses with two fields" $- basicExtraFields `parseMatch` goodBasic- it "parses with a fully-escaped field" $- basicEscaped `parseMatch` goodBasic- it "parses with a partially-escaped field" $- basicPartialEscape `parseMatch` goodBasic- it "parses an array" $- basicArray `parseMatch` replicate 4 goodBasic+ parseExample "true" True+ parseExample "false" False+ parseExample "null" ()+ describe "parsing numbers" $ do+ let m (t, e, i) = parseExample t $ scientific e i+ traverse_+ m+ [ ("0", 0, 0),+ ("1", 1, 0),+ ("20", 20, 0),+ ("190865", 190865, 0),+ ("-0", 0, 0),+ ("-1", -1, 0),+ ("-10.5", -105, -1),+ ("-1e100", -1, 100)+ ]+ describe "basic array parsing" $ do+ "[]" `parseExample` ([] :: [()])+ "[null ]" `parseExample` [()]+ "[null\n,null]" `parseExample` [(), ()]+ basicNull `parseExample` goodBasic+ basicArray `parseExample` replicate 4 goodBasic+ describe "basic object parsing" $ do+ basicOneFieldNoSpace `parseExample` goodBasic+ basicOneField `parseExample` goodBasic+ basicExtraFields `parseExample` goodBasic+ basicEscaped `parseExample` goodBasic+ basicEscapedNoSpace `parseExample` goodBasic+ basicPartialEscape `parseExample` goodBasic+ describe "parsing coordinates" $ do+ coordBasic `parseExample` Coord 10 10+ coordReverse `parseExample` Coord 12 11+ coordExtra `parseExample` Coord 1 2+ coordTuple `parseExample` Coord 1 20+ coordNoSpaceDecimal `parseExample` Coord 0 0+ describe "parsing homogenous coords" $ do+ "[1,2]" `parseExample` HomogenousCoord 1 2 1+ "[1,2,2]" `parseExample` HomogenousCoord 1 2 2+ "{ \"x\": 1, \"y\": 2, \"z\": 10 }" `parseExample` HomogenousCoord 1 2 10 describe "string parsing" $ do- it "parses with an escaped backslash" $ do- "\"foo\\\\\"" `parseMatch` ("foo\\" :: Text)- it "parses with an escaped quote" $ do- "\"foo\\\"\"" `parseMatch` ("foo\"" :: Text)- it "parses with unicode" $ do- "\"foo\\u2795\"" `parseMatch` ("foo➕" :: Text)- it "parses a text with no escapes" $ do- [r|"foo"|] `parseMatch` ("foo" :: Text)- it "parses a text with an escaped backslash" $ do- [r|"foo\\\\"|] `parseMatch` ([r|foo\\|] :: Text)+ "\"foo\\\\\"" `parseExample` ("foo\\" :: Text)+ "\"foo\\\"\"" `parseExample` ("foo\"" :: Text)+ "\"foo\\u2795\"" `parseExample` ("foo➕" :: Text)+ [r|"foo"|] `parseExample` ("foo" :: Text)+ [r|"foo\\\\"|] `parseExample` ([r|foo\\|] :: Text) describe "parsing a two-field object" $ do- it "parses with only required fields" $- twoSimple `parseMatch` goodTwo- it "parses with only required fields in wrong order" $- twoScramble `parseMatch` goodTwo- it "parses with lots of extra crap" $- twoExtra `parseMatch` goodTwo- it "parses in a weird order with extra crap" $- twoScrambleExtra `parseMatch` goodTwo+ twoSimple `parseExample` goodTwo+ twoScramble `parseExample` goodTwo+ twoExtra `parseExample` goodTwo+ twoScrambleExtra `parseExample` goodTwo describe "parsing a generically-derived object" $ do- it "parses correctly in the basic case" $- genericDefault `parseMatch` GenericStruct () [()]+ genericDefault `parseExample` GenericStruct () [()] describe "parsing a generally-derived sum object" $ do- it "parses first correctly" $- sumBasic `parseMatch` GenericBasic (BasicStruct ())- it "parses first correctrly when using other alternate" $- sumBasicObj `parseMatch` GenericBasic (BasicStruct ())- it "parses second correctly" $- sumTwo `parseMatch` GenericTwo (TwoFields () ())+ sumBasic `parseExample` GenericBasic (BasicStruct ())+ sumBasicObj `parseExample` GenericBasic (BasicStruct ())+ sumTwo `parseExample` GenericTwo (TwoFields () ())+ describe "object parsing with possible weirdness" $ do+ pack [r| {"general": "it sucked", "foo": "bar"} |] `parseExample` GeneralFeedback "it sucked"+ pack [r| {"foo": "bar"} |] `parseExample` SpecificFeedback [("foo", "bar")]
− test/Jordan/ToJSON/TextSpec.hs
@@ -1,85 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-module Jordan.ToJSON.TextSpec- where--import Data.Text (pack)-import GHC.Generics-import Jordan.SpecDefs (basicWritingSpec)-import Jordan.ToJSON.Class-import Jordan.ToJSON.Text-import Test.Hspec-import Text.RawString.QQ--shouldRenderJSON- :: (ToJSON a, Show a, HasCallStack)- => a- -> String- -> Expectation-shouldRenderJSON f a =- runJSONText toJSON f "" `shouldBe` pack a--data UselessTuple- = UselessTuple- { first :: ()- , second :: ()- } deriving (Show, Generic)--instance ToJSON UselessTuple--newtype NestedTuple- = NestedTuple- { getNestedTuple :: UselessTuple }- deriving (Show, Generic)--instance ToJSON NestedTuple--newtype WrapFoo- = WrapFoo { getFoo :: () }- deriving (Show, Generic)--instance ToJSON WrapFoo--newtype WrapBar- = WrapBar { getBar :: () }- deriving (Show, Generic)--instance ToJSON WrapBar--data PickOne- = PickFoo WrapFoo- | PickBar WrapBar- deriving (Show, Generic)--instance ToJSON PickOne--spec :: Spec-spec = describe "Jordan.ToJSON.Text" $ do- basicWritingSpec $ \v -> toJSONText v- arrayRendering- genericsRendering--arrayRendering :: Spec-arrayRendering = describe "array rendering" $ do- it "renders an empty array properly" $ do- ([] :: [()]) `shouldRenderJSON` "[]"- it "renders a one-item array properly" $ do- [()] `shouldRenderJSON` "[null]"- it "renders a two-item array properly" $ do- [(), ()] `shouldRenderJSON` "[null,null]"--genericsRendering :: Spec-genericsRendering = describe "generics rendering" $ do- it "can render a basic two-field object" $ do- UselessTuple () () `shouldRenderJSON` [r|{"first": null, "second": null}|]- it "can render a basic two-field object nested" $ do- NestedTuple (UselessTuple () ()) `shouldRenderJSON`- [r|{"getNestedTuple": {"first": null, "second": null}}|]- it "can render first case of PickOne" $ do- PickFoo (WrapFoo ()) `shouldRenderJSON`- [r|{"PickFoo": {"getFoo": null}}|]- it "can render second case of pickone" $ do- PickBar (WrapBar ()) `shouldRenderJSON`- [r|{"PickBar": {"getBar": null}}|]
test/JordanSpec.hs view
@@ -1,18 +1,17 @@ module Main- ( main- ) where+ ( main,+ )+where import qualified Jordan.FromJSON.AttoparsecSpec as APS-import qualified Jordan.FromJSON.MegaparsecSpec as MPS+import qualified Jordan.FromJSON.UnboxedReportingSpec as UR import qualified Jordan.RoundTripSpec as RTS import qualified Jordan.ToJSON.BuilderSpec as BS-import qualified Jordan.ToJSON.TextSpec as TS import Test.Hspec (hspec) main :: IO () main = hspec $ do- MPS.spec APS.spec- TS.spec BS.spec RTS.spec+ UR.spec