|
| 1 | +{-# LANGUAGE AllowAmbiguousTypes #-} |
| 2 | +{-# LANGUAGE DataKinds #-} |
| 3 | +{-# LANGUAGE DerivingStrategies #-} |
| 4 | +{-# LANGUAGE FlexibleContexts #-} |
| 5 | +{-# LANGUAGE FlexibleInstances #-} |
| 6 | +{-# LANGUAGE RankNTypes #-} |
| 7 | +{-# LANGUAGE TypeApplications #-} |
| 8 | + |
| 9 | +module JSONCompat where |
| 10 | + |
| 11 | +import Data.Aeson.Key (fromString) |
| 12 | +import Data.Aeson.Types (FromJSON (..), KeyValue ((.=)), Object, Parser, ToJSON (..), (.!=), (.:), (.:?)) |
| 13 | +import Data.Char (isUpper, toLower, toUpper) |
| 14 | +import Data.Default (Default (..)) |
| 15 | +import GHC.Records (HasField (..)) |
| 16 | +import GHC.TypeLits (KnownSymbol (..), SSymbol, fromSSymbol) |
| 17 | + |
| 18 | +kebabToCamel :: String -> String |
| 19 | +kebabToCamel = go False |
| 20 | + where |
| 21 | + go _ [] = [] |
| 22 | + go _ ('-' : cs) = go True cs |
| 23 | + go b (c : cs) = (if b then toUpper c else c) : go False cs |
| 24 | + |
| 25 | +camelToKebab :: String -> String |
| 26 | +camelToKebab = go . lowerFirst |
| 27 | + where |
| 28 | + lowerFirst [] = [] |
| 29 | + lowerFirst (c : cs) = toLower c : cs |
| 30 | + go (c : cs) |
| 31 | + | isUpper c = '-' : toLower c : go cs |
| 32 | + | otherwise = c : go cs |
| 33 | + go [] = [] |
| 34 | + |
| 35 | +newtype Getter r = Getter {unGetter :: forall f v e kv. SSymbol f -> (HasField f r v, KeyValue e kv, ToJSON v, Eq v) => r -> Maybe kv} |
| 36 | + |
| 37 | +get :: forall fld obj e kv a. (KnownSymbol fld, HasField fld obj a, KeyValue e kv, ToJSON a, Eq a) => Getter obj -> obj -> Maybe kv |
| 38 | +get (Getter getter) = getter (symbolSing @fld) |
| 39 | + |
| 40 | +always :: Getter r |
| 41 | +always = Getter $ \(fld :: SSymbol fld) obj -> |
| 42 | + let key = fromString (camelToKebab (fromSSymbol fld)) |
| 43 | + val = getField @fld obj |
| 44 | + in Just (key .= val) |
| 45 | + |
| 46 | +omitDefault :: Default r => Getter r |
| 47 | +omitDefault = Getter $ \(fld :: SSymbol fld) obj -> |
| 48 | + let key = fromString (camelToKebab (fromSSymbol fld)) |
| 49 | + getFld = getField @fld |
| 50 | + val = getFld obj |
| 51 | + in if val == getFld def then Nothing else Just (key .= val) |
| 52 | + |
| 53 | +parseFieldOrDefault :: forall obj fld a. (HasField fld obj a, Default obj, KnownSymbol fld, FromJSON a) => Object -> Parser a |
| 54 | +parseFieldOrDefault obj = |
| 55 | + obj .:? fromString (camelToKebab (fromSSymbol (symbolSing @fld))) .!= getField @fld (def :: obj) |
| 56 | + |
| 57 | +parseField :: forall obj fld a. (HasField fld obj a, KnownSymbol fld, FromJSON a) => Object -> Parser a |
| 58 | +parseField obj = obj .: fromString (camelToKebab (fromSSymbol (symbolSing @fld))) |
0 commit comments