winery
Sustainable serialisation library
https://github.com/fumieval/winery#readme
Version on this page: | 0.2.1 |
LTS Haskell 14.27: | 1.1.3 |
Stackage Nightly 2019-09-21: | 1.1.2 |
Latest on Hackage: | 1.5 |
winery-0.2.1@sha256:6d3a2d79fefb931980af69e69235c43370b3b83b0f875a8ccb8cb301c33136ea,2952
Module documentation for 0.2.1
winery
winery is a serialisation library for Haskell.
- Fast encoding: can create a bytestring or write to a handle efficiently
- Compact representation: uses VLQ by default. Separates schemata and contents
- Stateless decoding: you can decode a value without reading all the leading bytes
- Inspectable: data can be read without the original instance
Interface
The interface is simple; serialise
encodes a value with its schema, and
deserialise
decodes a ByteString using the schema in it.
class Serialise a
serialise :: Serialise a => a -> B.ByteString
deserialise :: Serialise a => B.ByteString -> Either String a
It’s also possible to serialise schemata and data separately.
-- Note that 'Schema' is an instance of 'Serialise'
schema :: Serialise a => proxy a -> Schema
serialiseOnly :: Serialise a => a -> B.ByteString
getDecoder
gives you a deserialiser.
getDecoder :: Serialise a => Schema -> Either StrategyError (ByteString -> a)
For user-defined datatypes, you can either define instances
instance Serialise Foo where
schemaVia = gschemaViaRecord
toEncoding = gtoEncodingRecord
deserialiser = gdeserialiserRecord Nothing
for single-constructor records, or just
instance Serialise Foo
for any ADT. The former explicitly describes field names in the schema, and the latter does constructor names.
Streaming output
You can write data to a handle without allocating a ByteString. You can see the length before serialisation.
toEncoding :: Serialise a => a -> Encoding
hPutEncoding :: Handle -> Encoding -> IO ()
getSize :: Encoding -> Int
The schema
The definition of Schema
is as follows:
data Schema = SSchema !Word8
| SUnit
| SBool
| SChar
| SWord8
| SWord16
| SWord32
| SWord64
| SInt8
| SInt16
| SInt32
| SInt64
| SInteger
| SFloat
| SDouble
| SBytes
| SText
| SList !Schema
| SArray !(VarInt Int) !Schema -- fixed size
| SProduct [Schema]
| SProductFixed [(VarInt Int, Schema)] -- fixed size
| SRecord [(T.Text, Schema)]
| SVariant [(T.Text, [Schema])]
| SFix Schema -- ^ binds a fixpoint
| SSelf !Word8 -- ^ @SSelf n@ refers to the n-th innermost fixpoint
deriving (Show, Read, Eq, Generic)
The Serialise
instance is derived by generics.
There are some special schemata:
SSchema n
is a schema of schema. The winery library stores the concrete schema ofSchema
for each version, so it can deserialise data even if the schema changes.SFix
binds a fixpoint.SSelf n
refers to the n-th innermost fixpoint bound bySFix
. This allows it to provide schemata for inductive datatypes.
Backward compatibility
If having default values for missing fields is sufficient, you can pass a
default value to gdeserialiserRecord
:
deserialiser = gdeserialiserRecord $ Just $ Foo "" 42 0
You can also build a custom deserialiser using the Alternative instance and combinators such as extractField
, extractConstructor
, etc.
Pretty-printing
Term
can be deserialised from any winery data. It can be pretty-printed using the Pretty
instance:
{ bar: "hello"
, baz: 3.141592653589793
, foo: Just 42
}
You can use the winery
command-line tool to inspect values.
$ winery '.[:10] | .first_name .last_name' benchmarks/data.winery
Shane Plett
Mata Snead
Levon Sammes
Irina Gourlay
Brooks Titlow
Antons Culleton
Regine Emerton
Starlin Laying
Orv Kempshall
Elizabeth Joseff
Cathee Eberz
At the moment, the following queries are supported:
.
return itself.[]
enumerate all the elements in a list.[i]
get the i-th element.[i:j]
enumerate i-th to j-th items.foo
Get a field namedfoo
F | G
compose queries (left to right)
Benchmark
data TestRec = TestRec
{ id_ :: !Int
, first_name :: !Text
, last_name :: !Text
, email :: !Text
, gender :: !Gender
, num :: !Int
, latitude :: !Double
, longitude :: !Double
} deriving (Show, Generic)
(De)serialisation of the datatype above using generic instances:
serialise/list/winery mean 847.4 μs ( +- 122.7 μs )
serialise/list/binary mean 1.221 ms ( +- 169.0 μs )
serialise/list/serialise mean 290.4 μs ( +- 34.98 μs )
serialise/item/winery mean 243.1 ns ( +- 27.50 ns )
serialise/item/binary mean 1.080 μs ( +- 75.82 ns )
serialise/item/serialise mean 322.4 ns ( +- 21.09 ns )
serialise/file/winery mean 681.9 μs ( +- 247.0 μs )
serialise/file/binary mean 1.731 ms ( +- 611.6 μs )
serialise/file/serialise mean 652.9 μs ( +- 185.8 μs )
deserialise/winery mean 733.2 μs ( +- 11.70 μs )
deserialise/binary mean 1.582 ms ( +- 122.3 μs )
deserialise/serialise mean 823.3 μs ( +- 38.08 μs )
Not bad, considering that binary and serialise don’t encode field names.
Changes
0.2
- Renamed
extract*With
toextract*By
for consistency - Added
hPut
- Improved the performance
- Added
-J
option towinery
which exports a JSON - Decoder now throws
DecodeException
rather than error calls
0.1.1
- Add
Data.Winery.Query
- The command line tool supports a simple query language
0.1
Overhauled the encoding; Sorry, incompatible with 0
0
Initial release