Haskell, Aeson - Is there a better way of parsing historical data?
Put a Map
in your data type. Aeson translates Map k v
s to/from objects, where the v
s are en-/de-coded via their own To
-/From
-JSON
instances and the k
s by To
-/From
-JSONKey
s. It turns out that Day
(from the time
package) has perfectly suitable To
-/From
-JSONKey
instances.
data EarthquakeData = EarthquakeData { metaData :: MetaData, earthquakes :: Map Day Earthquake} deriving (Eq, Show, Generic)instance FromJSON EarthquakeData where parseJSON = withObject "EarthquakeData $ \v -> EarthquakeData <$> v .: "Meta Data" -- Map k v has a FromJSON instance that just does the right thing -- so just get the payloads with (.:) -- all this code is actually just because your field names are really !#$@~?? -- not an Aeson expert, maybe there's a better way <*> v .: "EarthQuakes"instance ToJSON EarthquakeData where toJSON EarthquakeData{..} = object [ "Meta Data" .= metaData , "EarthQuakes" .= earthquakes ]data MetaData = MetaData { country :: String, region :: String, latestRec :: Day } deriving (Eq, Show)instance FromJSON MetaData where parseJSON = withObject "MetaData" $ \v -> -- if you haven't noticed, applicative style is much neater than do -- using OverloadedStrings avoids all the pack-ing static MetaData <$> v .: "1: Country" <*> v .: "2: Region" <*> v .: "3: Latest Recording"instance ToJSON MetaData where toJSON MetaData{..} = object [ "1: Country" .= country , "2: Region" .= region , "3: Latest Recording" .= latestRec ] toEncoding MetaData{..} = pairs $ "1: Country" .= country <> "2: Region" .= region <> "3: Latest Recording" .= latestRecdata Earthquake = Earthquake { richter :: Double } deriving (Eq, Show)-- Earthquake is a bit funky because your JSON apparently has-- numbers inside strings?-- only here do you actually need monadic operationsinstance FromJSON Earthquake where parseJSON = withObject "Earthquake" $ \v -> do string <- v .: "Richter" stringNum <- parseJSON string case readMaybe stringNum of Just num -> return $ Earthquake num Nothing -> typeMismatch "Double inside a String" stringinstance ToJSON Earthquake where toJSON = object . return . ("Richter" .=) . show . richter toEncoding = pairs . ("Richter" .=) . show . richter
I've tested this against your example JSON, and it appears to roundtrip encode
and decode
successfully.