deriving-aeson (empty) → 0
raw patch · 6 files changed
+199/−0 lines, 6 filesdep +aesondep +basedep +bytestringsetup-changed
Dependencies added: aeson, base, bytestring, deriving-aeson
Files
- CHANGELOG.md +5/−0
- LICENSE +30/−0
- Setup.hs +2/−0
- deriving-aeson.cabal +29/−0
- src/Deriving/Aeson.hs +113/−0
- tests/test.hs +20/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for deriving-aeson++## 0 -- 2020-02-26++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Fumiaki Kinoshita (c) 2020++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Fumiaki Kinoshita nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ deriving-aeson.cabal view
@@ -0,0 +1,29 @@+cabal-version: 2.4+name: deriving-aeson+version: 0+synopsis: Type driven generic aeson instance customisation+description: This package provides a newtype wrapper with+ FromJSON/ToJSON instances customisable via a phantom type parameter.+ The instances can be rendered to the original type using DerivingVia.+bug-reports: https://github.com/fumieval/deriving-aeson+license: BSD-3-Clause+license-file: LICENSE+author: Fumiaki Kinoshita+maintainer: fumiexcel@gmail.com+copyright: Copyright (c) 2020 Fumiaki Kinoshita+category: JSON, Generics+extra-source-files: CHANGELOG.md++library+ exposed-modules: Deriving.Aeson+ build-depends: base >= 4.12 && <5, aeson+ hs-source-dirs: src+ default-language: Haskell2010+ ghc-options: -Wall++test-suite test+ type: exitcode-stdio-1.0+ main-is: test.hs+ build-depends: base, aeson, deriving-aeson, bytestring+ hs-source-dirs: tests+ default-language: Haskell2010
+ src/Deriving/Aeson.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE UndecidableInstances #-}+--------------------+-- | Type-directed aeson instance CustomJSONisation+--------------------+module Deriving.Aeson+ ( CustomJSON(..)+ , FieldLabelModifier+ , ConstrctorTagModifier+ , OmitNothingFields+ , TagSingleConstructors+ , NoAllNullaryToStringTag+ -- * Name modifiers+ , StripPrefix+ , CamelToKebab+ , CamelToSnake+ -- * Interface+ , AesonOptions(..)+ , StringModifier(..)+ -- * Reexports+ , FromJSON+ , ToJSON+ , Generic+ )where++import Data.Aeson+import Data.Coerce+import Data.List (stripPrefix)+import Data.Maybe (fromMaybe)+import Data.Proxy+import GHC.Generics+import GHC.TypeLits++-- | A newtype wrapper which gives FromJSON/ToJSON instances with modified options.+newtype CustomJSON t a = CustomJSON { unCustomJSON :: a }++instance (AesonOptions t, Generic a, GFromJSON Zero (Rep a)) => FromJSON (CustomJSON t a) where+ parseJSON = (coerce `asTypeOf` fmap CustomJSON) . genericParseJSON (aesonOptions @t)+ {-# INLINE parseJSON #-}++instance (AesonOptions t, Generic a, GToJSON Zero (Rep a)) => ToJSON (CustomJSON t a) where+ toJSON = genericToJSON (aesonOptions @t) . unCustomJSON+ {-# INLINE toJSON #-}++-- | Function applied to field labels. Handy for removing common record prefixes for example.+data FieldLabelModifier t++-- | Function applied to constructor tags which could be handy for lower-casing them for example.+data ConstrctorTagModifier t++-- | Record fields with a Nothing value will be omitted from the resulting object.+data OmitNothingFields++-- | Encode types with a single constructor as sums, so that allNullaryToStringTag and sumEncoding apply.+data TagSingleConstructors++-- | the encoding will always follow the 'sumEncoding'.+data NoAllNullaryToStringTag++-- | Strip prefix @t@. If it doesn't have the prefix, keep it as-is.+data StripPrefix t++-- | CamelCase to snake_case+data CamelToSnake++-- | CamelCase to kebab-case+data CamelToKebab++-- | Reify a function which modifies names+class StringModifier t where+ getStringModifier :: String -> String++instance KnownSymbol k => StringModifier (StripPrefix k) where+ getStringModifier = fromMaybe <*> stripPrefix (symbolVal (Proxy @k))++-- | Left-to-right (@'flip' '.'@) composition+instance (StringModifier a, StringModifier b) => StringModifier (a, b) where+ getStringModifier = getStringModifier @b . getStringModifier @a++instance StringModifier CamelToKebab where+ getStringModifier = camelTo2 '-'++instance StringModifier CamelToSnake where+ getStringModifier = camelTo2 '_'++-- | Reify 'Options' from a type-level list+class AesonOptions xs where+ aesonOptions :: Options++instance AesonOptions '[] where+ aesonOptions = defaultOptions++instance AesonOptions xs => AesonOptions (OmitNothingFields ': xs) where+ aesonOptions = (aesonOptions @xs) { omitNothingFields = True }++instance (StringModifier f, AesonOptions xs) => AesonOptions (FieldLabelModifier f ': xs) where+ aesonOptions = (aesonOptions @xs) { fieldLabelModifier = getStringModifier @f }++instance (StringModifier f, AesonOptions xs) => AesonOptions (ConstrctorTagModifier f ': xs) where+ aesonOptions = (aesonOptions @xs) { constructorTagModifier = getStringModifier @f }++instance AesonOptions xs => AesonOptions (TagSingleConstructors ': xs) where+ aesonOptions = (aesonOptions @xs) { tagSingleConstructors = True }++instance AesonOptions xs => AesonOptions (NoAllNullaryToStringTag ': xs) where+ aesonOptions = (aesonOptions @xs) { allNullaryToStringTag = False }
+ tests/test.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE DerivingVia, DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+module Main where++import Data.Aeson+import qualified Data.ByteString.Lazy.Char8 as BL+import Deriving.Aeson++data User = User+ { userId :: Int+ , userName :: String+ , userAPIToken :: Maybe String+ } deriving Generic+ deriving (FromJSON, ToJSON)+ via CustomJSON '[OmitNothingFields, FieldLabelModifier (StripPrefix "user", CamelToSnake)] User++testData :: [User]+testData = [User 42 "Alice" Nothing, User 43 "Bob" (Just "xyz")]++main = BL.putStrLn $ encode $ testData