Codec makes it simple to write composable bidirectional serializers with a consistent interface.
Just define your data type normally:
dataRecordB=RecordB{recordBString::String
, recordBDouble::Double}deriving (Eq, Ord, Show)and then associate each field with a codec using the =. operator:
recordBObjCodec::JSONCodecRecordB
recordBObjCodec = asObject "RecordB"$RecordB<$> recordBString =. field "string"<*> recordBDouble =. field "double"That's it! If you want, you can now define ToJSON and FromJSON instances, or just use it directly:
instanceToJSONRecordBwhere
toJSON = toJSONCodec recordBObjCodec
toEncoding = toEncodingCodec recordBObjCodec
instanceFromJSONRecordBwhere
parseJSON = parseJSONCodec recordBObjCodecSupport can be added for almost any serialization library, but aeson and binary support are included.
JSON example:
dataRecordA=RecordA{recordAInt::Int
, recordANestedObj::RecordB
, recordANestedArr::RecordB
, recordANestedObjs:: [ RecordB ]
}deriving (Eq, Ord, Show)
dataRecordB=RecordB{recordBString::String
, recordBDouble::Double}deriving (Eq, Ord, Show)
recordACodec::JSONCodecRecordA
recordACodec = asObject "RecordA"$RecordA<$> recordAInt =. field "int"<*> recordANestedObj =. field' "nestedObj" recordBObjCodec
<*> recordANestedArr =. field' "nestedArr" recordBArrCodec
<*> recordANestedObjs =. field' "nestedObjs" (arrayOf' idid recordBObjCodec)
recordBObjCodec::JSONCodecRecordB
recordBObjCodec = asObject "RecordB"$RecordB<$> recordBString =. field "string"<*> recordBDouble =. field "double"-- serialize to array elementsrecordBArrCodec::JSONCodecRecordB
recordBArrCodec = asArray "RecordB"$RecordB<$> recordBString =. element
<*> recordBDouble =. elementBinary example:
dataRecordA=RecordA{recordAInt64::Int64
, recordAWord8::Word8
, recordANestedB::RecordB}deriving (Eq, Ord, Show)
dataRecordB=RecordB{recordBWord16::Word16
, recordBByteString64::BS.ByteString}deriving (Eq, Ord, Show)
recordACodec::BinaryCodecRecordA
recordACodec =RecordA<$> recordAInt64 =. int64le
<*> recordAWord8 =. word8
<*> recordANestedB =. recordBCodec
recordBCodec::BinaryCodecRecordB
recordBCodec =RecordB<$> recordBWord16 =. word16host
<*> recordBByteString64 =. byteString 64A Codec is just a combination of a deserializer r a, and a serializer c -> w a.
dataCodecForrwca=Codec{codecIn::ra
, codecOut::c->wa}typeCodecrwa=CodecForrwaaWith binary for example, r is Get and w is PutM. The reason we have an extra parameter c is so that we can associate a Codec with a particular field using the =. operator:
(=.) :: (c' -> c) -> CodecFor r w c a -> CodecFor r w c' a
Codec is an instance of Functor, Applicative, Monad and Profunctor. You can serialize in any order you like, regardless of field order in the data type:
recordBCodecFlipped::BinaryCodecRecordB
recordBCodecFlipped =do
bs64 <- recordBByteString64 =. byteString 64RecordB<$> recordBWord16 =. word16host
<*>pure bs64=. operator and Profunctor approach thanks to Xia Li-yao