-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Type-level JSON specification -- -- See the README at: -- https://github.com/owensmurray/json-spec#json-spec @package json-spec @version 0.3.0.0 -- | This module provides a way to specify the shape of your JSON data at -- the type level. -- --
-- data User = User
-- { name :: Text
-- , lastLogin :: UTCTime
-- }
-- deriving stock (Show, Eq)
-- deriving (ToJSON, FromJSON) via (SpecJSON User)
-- instance HasJsonEncodingSpec User where
-- type EncodingSpec User =
-- JsonObject '[
-- Required "name" JsonString,
-- Required "last-login" JsonDateTime
-- ]
-- toJSONStructure user =
-- (Field @"name" (name user),
-- (Field @"last-login" (lastLogin user),
-- ()))
-- instance HasJsonDecodingSpec User where
-- type DecodingSpec User = EncodingSpec User
-- fromJSONStructure
-- (Field @"name" name,
-- (Field @"last-login" lastLogin,
-- ()))
-- =
-- pure User { name , lastLogin }
--
--
-- -- type SpecWithNullableField = -- JsonObject '[ -- Required "nullableProperty" (JsonNullable JsonString) -- ] --JsonNullable :: Specification -> Specification -- | One of two different specifications. Corresponds to json-schema -- "oneOf". Useful for encoding sum types. E.g: -- --
-- data MyType -- = Foo Text -- | Bar Int -- | Baz UTCTime -- instance HasJsonEncodingSpec MyType where -- type EncodingSpec MyType = -- JsonEither -- ( -- JsonObject '[ -- Required "tag" (JsonTag "foo"), -- Required "content" JsonString -- ] -- ) -- ( -- JsonEither -- ( -- JsonObject '[ -- Required "tag" (JsonTag "bar"), -- Required "content" JsonInt -- ] -- ) -- ( -- JsonObject '[ -- Required "tag" (JsonTag "baz"), -- Required "content" JsonDateTime -- ] -- ) -- ) --JsonEither :: Specification -> Specification -> Specification -- | A constant string value JsonTag :: Symbol -> Specification -- | A JSON string formatted as an ISO-8601 string. In Haskell this -- corresponds to UTCTime, and in json-schema it corresponds to -- the "date-time" format. JsonDateTime :: Specification -- | A "let" expression. This is useful for giving names to types, which -- can then be used in the generated code. -- -- This is also useful to shorten repetitive type definitions. For -- example, this repetitive definition: -- --
-- type Triangle = -- JsonObject '[ -- Required "vertex1" (JsonObject '[ -- Required "x" JsonInt, -- Required "y" JsonInt, -- Required "z" JsonInt -- ]), -- Required "vertex2" (JsonObject '[ -- Required "x" JsonInt, -- Required "y" JsonInt, -- Required "z" JsonInt -- ]), -- Required "vertex3" (JsonObject '[ -- Required "x" JsonInt), -- Required "y" JsonInt), -- Required "z" JsonInt) -- ]) -- ] ---- -- Can be written more concisely as: -- --
-- type Triangle =
-- JsonLet
-- '[
-- '("Vertex", JsonObject '[
-- ('x', JsonInt),
-- ('y', JsonInt),
-- ('z', JsonInt)
-- ])
-- ]
-- (JsonObject '[
-- Required "vertex1" JsonRef "Vertex",
-- Required "vertex2" JsonRef "Vertex",
-- Required "vertex3" JsonRef "Vertex"
-- ])
--
--
-- Another use is to define recursive types:
--
--
-- type LabelledTree =
-- JsonLet
-- '[
-- '("LabelledTree", JsonObject '[
-- Required "label", JsonString,
-- Required "children" (JsonArray (JsonRef "LabelledTree"))
-- ])
-- ]
-- (JsonRef "LabelledTree")
--
JsonLet :: [(Symbol, Specification)] -> Specification -> Specification
-- | A reference to a specification which has been defined in a surrounding
-- JsonLet.
JsonRef :: Symbol -> Specification
-- | Types of this class can be encoded to JSON according to a type-level
-- Specification.
class HasJsonEncodingSpec a where {
-- | The encoding specification.
type EncodingSpec a :: Specification;
}
-- | Encode the value into the structure appropriate for the specification.
toJSONStructure :: HasJsonEncodingSpec a => a -> JSONStructure (EncodingSpec a)
-- | Types of this class can be JSON decoded according to a type-level
-- Specification.
class HasJsonDecodingSpec a where {
-- | The decoding Specification.
type DecodingSpec a :: Specification;
}
-- | Given the structural encoding of the JSON data, parse the structure
-- into the final type. The reason this returns a Parser
-- a instead of just a plain a is because there may still
-- be some invariants of the JSON data that the Specification
-- language is not able to express, and so you may need to fail parsing
-- in those cases. For instance, Specification is not powerful
-- enough to express "this field must contain only prime numbers".
fromJSONStructure :: HasJsonDecodingSpec a => JSONStructure (DecodingSpec a) -> Parser a
-- | Helper for defining ToJSON and FromJSON instances based
-- on HasEncodingJsonSpec.
--
-- Use with -XDerivingVia like:
--
--
-- data MyObj = MyObj
-- { foo :: Int
-- , bar :: Text
-- }
-- deriving (ToJSON, FromJSON) via (SpecJSON MyObj)
-- instance HasEncodingSpec MyObj where ...
-- instance HasDecodingSpec MyObj where ...
--
newtype SpecJSON a
SpecJSON :: a -> SpecJSON a
[unSpecJson] :: SpecJSON a -> a
-- | Structural representation of JsonTag. (I.e. a constant string
-- value.)
data Tag (a :: Symbol)
Tag :: Tag (a :: Symbol)
-- | Structural representation of an object field.
newtype Field (key :: Symbol) t
Field :: t -> Field (key :: Symbol) t
[unField] :: Field (key :: Symbol) t -> t
-- | JSONStructure spec is the Haskell type used to contain
-- the JSON data that will be encoded or decoded according to the
-- provided spec.
--
-- Basically, we represent JSON objects as "list-like" nested tuples of
-- the form:
--
-- -- (Field @key1 valueType, -- (Field @key2 valueType, -- (Field @key3 valueType, -- ()))) ---- -- Arrays, booleans, numbers, and strings are just Lists, Bools, -- Scientifics, and Texts respectively. -- -- If the user can convert their normal business logic type to/from this -- tuple type, then they get a JSON encoding to/from their type that is -- guaranteed to be compliant with the Specification type family JSONStructure (spec :: Specification) -- | This allows for recursive specifications. -- -- Since the specification is at the type level, and type level haskell -- is strict, specifying a recursive definition the "naive" way would -- cause an infinitely sized type. -- -- For example this won't work: -- --
-- data Foo = Foo [Foo] -- instance HasJsonEncodingSpec Foo where -- type EncodingSpec Foo = JsonArray (EncodingSpec Foo) -- toJSONStructure = ... can't be written ---- -- Using JsonLet prevents the specification type from being -- infinitely sized, but what about "structure" type which holds real -- values corresponding to the spec? The structure type has to have some -- way to reference itself or else it too would be infinitely sized. -- -- In order to "reference itself" the structure type has to go through a -- newtype somewhere along the way, and that's what this type is for. -- Whenever the structure type for your spec requires a self-reference, -- it will require you to wrap the recursed upon values in this type. -- -- For example: -- --
-- data Foo = Foo [Foo]
-- instance HasJsonEncodingSpec Foo where
-- type EncodingSpec Foo =
-- JsonLet
-- '[ '("Foo", JsonArray (JsonRef "Foo")) ]
-- (JsonRef "Foo")
-- toJSONStructure (Foo fs) =
-- [ Rec (toJSONStructure f)
-- | f <- fs
-- ]
--
newtype Rec (env :: [(Symbol, Type)]) (name :: Symbol) (spec :: Specification)
Rec :: JStruct ('(name, Rec env name spec) ': env) spec -> Rec (env :: [(Symbol, Type)]) (name :: Symbol) (spec :: Specification)
[unRec] :: Rec (env :: [(Symbol, Type)]) (name :: Symbol) (spec :: Specification) -> JStruct ('(name, Rec env name spec) ': env) spec
-- | Directly decode some JSON accoring to a spec without going through any
-- To/FromJSON instances.
eitherDecode :: forall (spec :: Specification). StructureFromJSON (JSONStructure spec) => Proxy spec -> Value -> Either String (JSONStructure spec)
-- | Analog of FromJSON, but specialized for decoding our "json
-- representations", and closed to the user because the haskell
-- representation scheme is fixed and not extensible by the user.
--
-- We can't just use FromJSON because the types we are using to
-- represent "json data" (i.e. the JSONStructure type family)
-- already have ToJSON instances. Even if we were to make a
-- bunch of newtypes or whatever to act as the json representation (and
-- therefor also force the user to do a lot of wrapping and unwrapping),
-- that still wouldn't be sufficient because someone could always write
-- an overlapping (or incoherent) ToJSON instance of our
-- newtype! This way we don't have to worry about any of that, and the
-- types that the user must deal with when implementing
-- fromJSONRepr can be simple tuples and such.
class StructureFromJSON a
-- | Specify a field in an object.
data FieldSpec
-- | The field is required
Required :: Symbol -> Specification -> FieldSpec
-- | The field is optionsl
Optional :: Symbol -> Specification -> FieldSpec
instance (Data.JsonSpec.Decode.StructureFromJSON (Data.JsonSpec.Spec.JSONStructure (Data.JsonSpec.Decode.DecodingSpec a)), Data.JsonSpec.Decode.HasJsonDecodingSpec a) => Data.Aeson.Types.FromJSON.FromJSON (Data.JsonSpec.SpecJSON a)
instance (Data.JsonSpec.Encode.StructureToJSON (Data.JsonSpec.Spec.JSONStructure (Data.JsonSpec.Encode.EncodingSpec a)), Data.JsonSpec.Encode.HasJsonEncodingSpec a) => Data.Aeson.Types.ToJSON.ToJSON (Data.JsonSpec.SpecJSON a)