json-spec
Type-level JSON specification
https://github.com/owensmurray/json-spec
| LTS Haskell 24.58: | 1.1.1.2 |
| Stackage Nightly 2026-09-10: | 1.4.0.1 |
| Latest on Hackage: | 1.4.0.1 |
json-spec-1.4.0.1@sha256:042d100e0c7b6d272b20981e3c78678bfe3e8d2f28d9138ba2cdf2d78b7fbc61,1937Module documentation for 1.4.0.1
- Data
json-spec
Motivation
The primary motivation is to allow you to avoid Aeson Generic instances while still getting the possibility of auto-generated (and therefore /correct/) documentation and code in your servant APIs.
Historically, the trade-off has been:
-
Use Generic instances, and therefore your API is brittle. Changes to a deeply nested object might unexpectedly change (and break) your API. You must structure your Haskell types exactly as they are rendered into JSON, which may not always be “natural” and easy to work with. In exchange, you get the ability to auto-derive matching ToSchema instances along with various code generation tools that all understand Aeson Generic instances.
-
Hand-write your ToJSON and FromJSON instances, which means you get to structure your Haskell types in the way that works best for Haskell, while structuring your JSON in the way that works best for your API. It also means you can more easily support “old” decoding versions and more easily maintain backwards compatibility, etc. In exchange, you have to to hand-write your ToSchema instances, and code generation is basically out.
The goal of this library is to provide a way to hand-write the encoding and decoding of your JSON using type-level ‘Specification’s, while still allowing the use of tools that can interpret the specification and auto-generate ToSchema instances and code.
The tooling ecosystem that knows how to interpret ‘Specification’s is still pretty new, but it at least includes OpenApi compatibility (i.e. ToSchema instances) and Elm code generation.
Example
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 }
For more examples, take a look at the test suite.
JsonDict represents a JSON object whose keys are arbitrary strings and
whose values all conform to one spec:
type ScoresByUser = JsonDict JsonInt
Changes
Changelog
1.4.0.1
- Relax the
aesonupper bound to allow 2.3.x.
1.4.0.0
- Add
JsonDictfor JSON objects with arbitrary string keys and values that all conform to one known specification.
1.3.0.2
- Relax upper bounds on
containersandtimefor current Hackage packages.
1.3.0.1
- Support GHC 9.14.
1.3.0.0
JsonEither now takes a type-level list
JsonEither now accepts a type-level list of specs (JsonEither '[a, b, c])
instead of two arguments (JsonEither a b), so sum types with many branches
no longer require a binary tree of nested JsonEithers. The structural type
for JsonEither is nested Either: two or more branches map to
Either (JStruct env a) (Either (JStruct env b) ...); a single branch maps
to JStruct env spec (no sum wrapper). Use Left/Right for construction
and pattern matching.
Migration guide
Specs (example: four alternatives)
Before:
JsonEither (JsonEither (JsonEither specA specB) specC) specD
After:
JsonEither '[specA, specB, specC, specD]
Patterns/construction
Note: The only difference in the pattern/construction may be how
the Eithers are nested. The two examples below represent the same
four alternatives with different Left/Right nesting; the JSON and
types are equivalent.
Before (four branches):
Left (Left (Left val))
Left (Left (Right val))
Left (Right val)
Right val
After (same nesting with Left/Right; one branch = no wrapper):
Left val
Right (Left val)
Right (Right (Left val))
Right (Right (Right val))