packages feed

safe-money-aeson (empty) → 0.1

raw patch · 7 files changed

+507/−0 lines, 7 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, safe-money, safe-money-aeson, tasty, tasty-hunit, tasty-quickcheck, text

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016-2018, Renzo Carbonara++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 Renzo Carbonara 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.
+ README.md view
@@ -0,0 +1,7 @@+This library exports `FromJSON` and `ToJSON` instances (from the+`aeson` library) for many of the types exported by the `safe-money`+library.++Note: The code in this library used to be part of the `safe-money`+library itself, so these instances are intended to be backwards+compatible with older versions of `safe-money`.
+ Setup.hs view
@@ -0,0 +1,4 @@+#! /usr/bin/env nix-shell+#! nix-shell ./shell.nix -i runghc+import Distribution.Simple+main = defaultMain
+ changelog.md view
@@ -0,0 +1,4 @@+# Version 0.1++* This first release of @safe-money-aeson@ includes the same @aeson@ support+  and tests that were present in @safe-money-0.6@.
+ safe-money-aeson.cabal view
@@ -0,0 +1,56 @@+name: safe-money-aeson+version: 0.1+license: BSD3+license-file: LICENSE+copyright: Copyright (c) Renzo Carbonara 2016-2018+author: Renzo Carbonara+maintainer: renλren!zone+stability: Experimental+tested-with: GHC==8.4.1+homepage: https://github.com/k0001/safe-money+bug-reports: https://github.com/k0001/safe-money/issues+category: Money+build-type: Simple+cabal-version: >=1.10+extra-source-files: README.md changelog.md+synopsis: Instances from the aeson library for the safe-money library.+description:+  This library exports @FromJSON@ and @ToJSON@ instances (from the+  @aeson@ library) for many of the types exported by the @safe-money@+  library.+  .+  Note: The code in this library used to be part of the @safe-money@+  library itself, so these instances are intended to be backwards+  compatible with older versions of @safe-money@.++source-repository head+  type: git+  location: https://github.com/k0001/safe-money++library+  default-language: Haskell2010+  hs-source-dirs: src+  ghc-options: -Wall -O2+  build-depends:+    aeson,+    base >=4.8 && <5.0,+    safe-money >=0.7,+    text+  exposed-modules:+    Money.Aeson++test-suite test+  default-language: Haskell2010+  type: exitcode-stdio-1.0+  hs-source-dirs: test+  main-is: Main.hs+  build-depends:+    aeson,+    base,+    bytestring,+    safe-money,+    safe-money-aeson,+    tasty,+    tasty-hunit,+    tasty-quickcheck,+    text
+ src/Money/Aeson.hs view
@@ -0,0 +1,166 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -Wno-orphans #-}++-- | This module only exports orphan 'Ae.FromJSON' and 'Ae.ToJSON' instances.+-- Import as:+--+-- @+-- import "Money.Aeson" ()+-- @+module Money.Aeson () where++import Control.Applicative ((<|>), empty)+import Control.Monad ((<=<), when)+import qualified Data.Aeson as Ae+import Data.Ratio ((%), numerator, denominator)+import qualified Data.Text as T+import GHC.TypeLits (KnownSymbol)+import qualified Money+import qualified Money.Internal as MoneyI++--------------------------------------------------------------------------------+-- | Compatible with 'Money.SomeDense'+--+-- Example rendering @'Money.dense'' (2 '%' 3) :: 'Money.Dense' \"BTC\"@:+--+-- @+-- [\"BTC\", 2, 3]+-- @+--+-- Note: The JSON serialization changed in version 0.4 (the leading @"Dense"@+-- string was dropped from the rendered 'Ae.Array').+instance KnownSymbol currency => Ae.ToJSON (Money.Dense currency) where+  toJSON = Ae.toJSON . Money.toSomeDense++-- | Compatible with 'Money.SomeDense'+--+-- Note: The JSON serialization changed in @safe-money@ version 0.4. However,+-- this instance is still able to cope with the previous format.+instance KnownSymbol currency => Ae.FromJSON (Money.Dense currency) where+  parseJSON = maybe empty pure <=< fmap Money.fromSomeDense . Ae.parseJSON++-- | Compatible with 'Money.Dense'+--+-- Note: The JSON serialization changed in @safe-money@ version 0.4 (the leading+-- @"Dense"@ string was dropped from the rendered 'Ae.Array').+instance Ae.ToJSON Money.SomeDense where+  toJSON = \sd ->+    let r = Money.someDenseAmount sd+    in Ae.toJSON (MoneyI.someDenseCurrency' sd, numerator r, denominator r)++-- | Compatible with 'Money.Dense'.+--+-- Note: The JSON serialization changed in @safe-money@ version 0.4. However,+-- this instance is still able to cope with the previous format.+instance Ae.FromJSON Money.SomeDense where+  parseJSON = \v -> do+    (c, n, d) <- Ae.parseJSON v <|> do+       -- Pre 0.4 format.+       ("Dense" :: String, c, n, d) <- Ae.parseJSON v+       pure (c, n, d)+    when (d == 0) (fail "denominator is zero")+    maybe empty pure (MoneyI.mkSomeDense' c (n % d))++-- | Compatible with 'Money.SomeDiscrete'+--+-- Example rendering @'Money.discrete' 43 :: 'Money.Discrete' \"BTC\" \"satoshi\"@:+--+-- @+-- [\"BTC\", 100000000, 1, 43]+-- @+--+-- Note: The JSON serialization changed in @safe-money@ version 0.4 (the leading+-- @"Discrete"@ string was dropped from the rendered 'Ae.Array').+instance+  ( KnownSymbol currency, Money.GoodScale scale+  ) => Ae.ToJSON (Money.Discrete' currency scale) where+  toJSON = Ae.toJSON . Money.toSomeDiscrete++-- | Compatible with 'Money.SomeDiscrete'+--+-- Note: The JSON serialization changed in @safe-money@ version 0.4. However,+-- this instance is still able to cope with the previous format.+instance+  ( KnownSymbol currency, Money.GoodScale scale+  ) => Ae.FromJSON (Money.Discrete' currency scale) where+  parseJSON = maybe empty pure <=< fmap Money.fromSomeDiscrete . Ae.parseJSON++-- | Compatible with 'Money.Discrete''+--+-- Note: The JSON serialization changed in version 0.4 (the leading @"Discrete"@+-- string was dropped from the rendered 'Ae.Array').+instance Ae.ToJSON Money.SomeDiscrete where+  toJSON = \sd ->+    let r = Money.someDiscreteScale sd+    in Ae.toJSON (MoneyI.someDiscreteCurrency' sd,+                  numerator r, denominator r,+                  Money.someDiscreteAmount sd)++-- | Compatible with 'Money.Discrete''+--+-- Note: The JSON serialization changed in version 0.4. However, this instance+-- is still able to cope with the previous format.+instance Ae.FromJSON Money.SomeDiscrete where+  parseJSON = \v -> do+    (c, n, d, a) <- Ae.parseJSON v <|> do+       -- Pre 0.4 format.+       ("Discrete" :: T.Text, c, n, d, a) <- Ae.parseJSON v+       pure (c, n, d, a)+    when (d == 0) (fail "denominator is zero")+    maybe empty pure (MoneyI.mkSomeDiscrete' c (n % d) a)++-- | Compatible with 'Money.SomeExchangeRate'+--+-- Example rendering an 'Money.ExchangeRate' constructed with+-- @'Money.exchangeRate' (5 '%' 7) :: 'Money.ExchangeRate' \"USD\" \"JPY\"@+--+-- @+-- [\"USD\", \"JPY\", 5, 7]+-- @+--+-- Note: The JSON serialization changed in version 0.4 (the leading+-- @"ExchangeRate"@ string was dropped from the rendered 'Ae.Array').+instance+  ( KnownSymbol src, KnownSymbol dst+  ) => Ae.ToJSON (Money.ExchangeRate src dst) where+  toJSON = Ae.toJSON . Money.toSomeExchangeRate++-- | Compatible with 'Money.SomeExchangeRate'+--+-- Note: The JSON serialization changed in version 0.4. However, this instance+-- is still able to cope with the previous format.+instance+  ( KnownSymbol src, KnownSymbol dst+  ) => Ae.FromJSON (Money.ExchangeRate src dst) where+  parseJSON =+    maybe empty pure <=< fmap Money.fromSomeExchangeRate . Ae.parseJSON++-- | Compatible with 'Money.ExchangeRate'+--+-- Note: The JSON serialization changed in @safe-money@ version 0.4 (the leading+-- @"ExchangeRate"@ string was dropped from the rendered 'Ae.Array').+instance Ae.ToJSON Money.SomeExchangeRate where+  toJSON = \ser ->+    let r = Money.someExchangeRateRate ser+    in Ae.toJSON (MoneyI.someExchangeRateSrcCurrency' ser,+                  MoneyI.someExchangeRateDstCurrency' ser,+                  numerator r, denominator r)++-- | Compatible with 'Money.ExchangeRate'+--+-- Note: The JSON serialization changed in @safe-money@ version 0.4. However,+-- this instance is still able to cope with the previous format.+instance Ae.FromJSON Money.SomeExchangeRate where+  parseJSON = \v -> do+    (src, dst, n, d) <- Ae.parseJSON v <|> do+       -- Pre 0.4 format.+       ("ExchangeRate" :: T.Text, src, dst, n, d) <- Ae.parseJSON v+       pure (src, dst, n, d)+    when (d == 0) (fail "denominator is zero")+    maybe empty pure (MoneyI.mkSomeExchangeRate' src dst (n % d))+
+ test/Main.hs view
@@ -0,0 +1,240 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeApplications #-}++module Main where++import qualified Data.Aeson as Ae+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import qualified Data.Char as Char+import Data.Proxy (Proxy(Proxy))+import Data.Ratio ((%), numerator, denominator)+import qualified Data.Text as T+import Data.Word (Word8)+import GHC.Exts (fromList)+import GHC.TypeLits (Nat, Symbol, KnownSymbol, symbolVal)+import qualified Money+import qualified Test.Tasty as Tasty+import Test.Tasty.HUnit ((@?=), (@=?))+import qualified Test.Tasty.HUnit as HU+import qualified Test.Tasty.Runners as Tasty+import Test.Tasty.QuickCheck ((===), (==>), (.&&.))+import qualified Test.Tasty.QuickCheck as QC++import qualified Money.Aeson++--------------------------------------------------------------------------------++main :: IO ()+main =  Tasty.defaultMainWithIngredients+  [ Tasty.consoleTestReporter+  , Tasty.listingTests+  ] (Tasty.localOption (QC.QuickCheckTests 100) tests)++tests :: Tasty.TestTree+tests =+  Tasty.testGroup "root"+  [ testCurrencies+  , testCurrencyUnits+  , testExchange+  , testRawSerializations+  ]++testCurrencies :: Tasty.TestTree+testCurrencies =+  Tasty.testGroup "Currency"+  [ testDense (Proxy :: Proxy "BTC")  -- A cryptocurrency.+  , testDense (Proxy :: Proxy "USD")  -- A fiat currency with decimal fractions.+  , testDense (Proxy :: Proxy "VUV")  -- A fiat currency with non-decimal fractions.+  , testDense (Proxy :: Proxy "XAU")  -- A precious metal.+  ]++testCurrencyUnits :: Tasty.TestTree+testCurrencyUnits =+  Tasty.testGroup "Currency units"+  [ testDiscrete (Proxy :: Proxy "BTC") (Proxy :: Proxy "BTC")+  , testDiscrete (Proxy :: Proxy "BTC") (Proxy :: Proxy "satoshi")+  , testDiscrete (Proxy :: Proxy "BTC") (Proxy :: Proxy "bitcoin")+  , testDiscrete (Proxy :: Proxy "USD") (Proxy :: Proxy "USD")+  , testDiscrete (Proxy :: Proxy "USD") (Proxy :: Proxy "cent")+  , testDiscrete (Proxy :: Proxy "USD") (Proxy :: Proxy "dollar")+  , testDiscrete (Proxy :: Proxy "VUV") (Proxy :: Proxy "vatu")+  , testDiscrete (Proxy :: Proxy "XAU") (Proxy :: Proxy "gram")+  , testDiscrete (Proxy :: Proxy "XAU") (Proxy :: Proxy "grain")+  ]++testDense+  :: forall currency+  .  KnownSymbol currency+  => Proxy currency+  -> Tasty.TestTree+testDense pc =+  Tasty.testGroup ("Dense " ++ show (symbolVal pc))+  [ QC.testProperty "Aeson encoding roundtrip" $+      QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->+         Just x === Ae.decode (Ae.encode x)+  , QC.testProperty "Aeson encoding roundtrip (SomeDense)" $+      QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->+         let x' = Money.toSomeDense x+         in Just x' === Ae.decode (Ae.encode x')+  , QC.testProperty "Aeson encoding roundtrip (Dense through SomeDense)" $+      QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->+         Just x === Ae.decode (Ae.encode (Money.toSomeDense x))+  , QC.testProperty "Aeson encoding roundtrip (SomeDense through Dense)" $+      QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->+         Just (Money.toSomeDense x) === Ae.decode (Ae.encode x)+  , QC.testProperty "Aeson decoding of pre-0.4 format (Dense, SomeDense)" $+      QC.forAll QC.arbitrary $ \(x :: Money.Dense currency) ->+        let sx = Money.toSomeDense x+            c = T.unpack (Money.someDenseCurrency sx)+            r = Money.someDenseAmount sx+            bs = Ae.encode ("Dense" :: String, c, numerator r, denominator r)+        in (Just  x === Ae.decode bs) .&&.+           (Just sx === Ae.decode bs)+  ]++testExchange :: Tasty.TestTree+testExchange =+  Tasty.testGroup "Exchange"+  [ testExchangeRate (Proxy :: Proxy "BTC") (Proxy :: Proxy "BTC")+  , testExchangeRate (Proxy :: Proxy "BTC") (Proxy :: Proxy "USD")+  , testExchangeRate (Proxy :: Proxy "BTC") (Proxy :: Proxy "VUV")+  , testExchangeRate (Proxy :: Proxy "BTC") (Proxy :: Proxy "XAU")+  , testExchangeRate (Proxy :: Proxy "USD") (Proxy :: Proxy "BTC")+  , testExchangeRate (Proxy :: Proxy "USD") (Proxy :: Proxy "USD")+  , testExchangeRate (Proxy :: Proxy "USD") (Proxy :: Proxy "VUV")+  , testExchangeRate (Proxy :: Proxy "USD") (Proxy :: Proxy "XAU")+  , testExchangeRate (Proxy :: Proxy "VUV") (Proxy :: Proxy "BTC")+  , testExchangeRate (Proxy :: Proxy "VUV") (Proxy :: Proxy "USD")+  , testExchangeRate (Proxy :: Proxy "VUV") (Proxy :: Proxy "VUV")+  , testExchangeRate (Proxy :: Proxy "VUV") (Proxy :: Proxy "XAU")+  , testExchangeRate (Proxy :: Proxy "XAU") (Proxy :: Proxy "BTC")+  , testExchangeRate (Proxy :: Proxy "XAU") (Proxy :: Proxy "USD")+  , testExchangeRate (Proxy :: Proxy "XAU") (Proxy :: Proxy "VUV")+  , testExchangeRate (Proxy :: Proxy "XAU") (Proxy :: Proxy "XAU")+  ]+++testDiscrete+  :: forall (currency :: Symbol) (unit :: Symbol)+  .  ( Money.GoodScale (Money.Scale currency unit)+     , KnownSymbol currency+     , KnownSymbol unit )+  => Proxy currency+  -> Proxy unit+  -> Tasty.TestTree+testDiscrete pc pu =+  Tasty.testGroup ("Discrete " ++ show (symbolVal pc) ++ " "+                               ++ show (symbolVal pu))+  [ QC.testProperty "Aeson encoding roundtrip" $+      QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit) ->+         Just x === Ae.decode (Ae.encode x)+  , QC.testProperty "Aeson encoding roundtrip (SomeDiscrete)" $+      QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit) ->+         let x' = Money.toSomeDiscrete x+         in Just x' === Ae.decode (Ae.encode x')+  , QC.testProperty "Aeson encoding roundtrip (Discrete through SomeDiscrete)" $+      QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit) ->+         Just x === Ae.decode (Ae.encode (Money.toSomeDiscrete x))+  , QC.testProperty "Aeson encoding roundtrip (SomeDiscrete through Discrete)" $+      QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit) ->+         Just (Money.toSomeDiscrete x) === Ae.decode (Ae.encode x)+  , QC.testProperty "Aeson decoding of pre-0.4 format (Discrete, SomeDiscrete)" $+      QC.forAll QC.arbitrary $ \(x :: Money.Discrete currency unit) ->+        let sx = Money.toSomeDiscrete x+            c = T.unpack (Money.someDiscreteCurrency sx)+            r = Money.someDiscreteScale sx+            a = Money.someDiscreteAmount sx+            bs = Ae.encode ("Discrete" :: String, c, numerator r, denominator r, a)+        in (Just  x === Ae.decode bs) .&&.+           (Just sx === Ae.decode bs)+  ]++testExchangeRate+  :: forall (src :: Symbol) (dst :: Symbol)+  .  (KnownSymbol src, KnownSymbol dst)+  => Proxy src+  -> Proxy dst+  -> Tasty.TestTree+testExchangeRate ps pd =+  Tasty.testGroup ("ExchangeRate " ++ show (symbolVal ps) ++ " "+                                   ++ show (symbolVal pd))+  [ QC.testProperty "Aeson encoding roundtrip" $+      QC.forAll QC.arbitrary $ \(x :: Money.ExchangeRate src dst) ->+         Just x === Ae.decode (Ae.encode x)+  , QC.testProperty "Aeson encoding roundtrip (SomeExchangeRate)" $+      QC.forAll QC.arbitrary $ \(x :: Money.ExchangeRate src dst) ->+         let x' = Money.toSomeExchangeRate x+         in Just x' === Ae.decode (Ae.encode x')+  , QC.testProperty "Aeson encoding roundtrip (ExchangeRate through SomeExchangeRate)" $+      QC.forAll QC.arbitrary $ \(x :: Money.ExchangeRate src dst) ->+         Just x === Ae.decode (Ae.encode (Money.toSomeExchangeRate x))+  , QC.testProperty "Aeson encoding roundtrip (SomeExchangeRate through ExchangeRate)" $+      QC.forAll QC.arbitrary $ \(x :: Money.ExchangeRate src dst) ->+         Just (Money.toSomeExchangeRate x) === Ae.decode (Ae.encode x)+  , QC.testProperty "Aeson decoding of pre-0.4 format (ExchangeRate, SomeExchangeRate)" $+      QC.forAll QC.arbitrary $ \(x :: Money.ExchangeRate src dst) ->+        let sx = Money.toSomeExchangeRate x+            src = T.unpack (Money.someExchangeRateSrcCurrency sx)+            dst = T.unpack (Money.someExchangeRateDstCurrency sx)+            r = Money.someExchangeRateRate sx+            bs = Ae.encode ("ExchangeRate" :: String, src, dst, numerator r, denominator r)+        in (Just  x === Ae.decode bs) .&&.+           (Just sx === Ae.decode bs)+  ]++--------------------------------------------------------------------------------+-- Raw parsing "golden tests"++testRawSerializations :: Tasty.TestTree+testRawSerializations =+  Tasty.testGroup "Raw serializations"+  [ Tasty.testGroup "aeson"+    [ Tasty.testGroup "decode"+      [ HU.testCase "Dense" $ Just rawDns0 @=? Ae.decode rawDns0_aeson+      , HU.testCase "Discrete" $ Just rawDis0 @=? Ae.decode rawDis0_aeson+      , HU.testCase "ExchangeRate" $ Just rawXr0 @=? Ae.decode rawXr0_aeson+      ]+    , Tasty.testGroup "decode (pre-0.4)"+      [ HU.testCase "Dense" $ Just rawDns0 @=? Ae.decode rawDns0_aeson_pre04+      , HU.testCase "Discrete" $ Just rawDis0 @=? Ae.decode rawDis0_aeson_pre04+      , HU.testCase "ExchangeRate" $ Just rawXr0 @=? Ae.decode rawXr0_aeson_pre04+      ]+    , Tasty.testGroup "encode"+      [ HU.testCase "Dense" $ rawDns0_aeson @=? Ae.encode rawDns0+      , HU.testCase "Discrete" $ rawDis0_aeson @=? Ae.encode rawDis0+      , HU.testCase "ExchangeRate" $ rawXr0_aeson @=? Ae.encode rawXr0+      ]+    ]+  ]++rawDns0 :: Money.Dense "USD"+rawDns0 = Money.dense' (26%1)++rawDis0 :: Money.Discrete "USD" "cent"+rawDis0 = Money.discrete 4++rawXr0 :: Money.ExchangeRate "USD" "BTC"+Just rawXr0 = Money.exchangeRate (3%2)++rawDns0_aeson :: BL.ByteString+rawDns0_aeson = "[\"USD\",26,1]"+rawDis0_aeson :: BL.ByteString+rawDis0_aeson = "[\"USD\",100,1,4]"+rawXr0_aeson :: BL.ByteString+rawXr0_aeson = "[\"USD\",\"BTC\",3,2]"++-- pre safe-money 0.4+rawDns0_aeson_pre04 :: BL.ByteString+rawDns0_aeson_pre04 = "[\"Dense\",\"USD\",26,1]"+rawDis0_aeson_pre04 :: BL.ByteString+rawDis0_aeson_pre04 = "[\"Discrete\",\"USD\",100,1,4]"+rawXr0_aeson_pre04 :: BL.ByteString+rawXr0_aeson_pre04 = "[\"ExchangeRate\",\"USD\",\"BTC\",3,2]"+