7

I am trying to encode a data type into JSON:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

import Data.Aeson

data Trend = Trend
            { period :: String
            , africa :: String
            , americas :: String
            , asia :: String
            } deriving Show

instance ToJSON Trend where
  toJSON Trend{..} = 
    object [ "Period"    .= period
           , "Africa"    .= africa
           , "Americas"  .= americas
           , "Asia"      .= asia
           ]

test = Trend {period = "2013", africa = "1", americas = "2", asia = "3"}

Which gives:

λ: encode test
λ: "{\"Asia\":\"3\",\"Period\":\"2013\",\"Africa\":\"1\",\"Americas\":\"2\"}"

I don't understand why the generated JSON does not have the fields in the same order as my data type.

I am expecting the output to be {period, africa, americas, asia} and I am getting {asia, period, africa, americas)

I understand that in passing information across, the order is not important but I am curious as to why this is happening.

matt
  • 1,817
  • 14
  • 35
  • 7
    JSON has no 'correct' order of fields. edit: see the answer [here](http://stackoverflow.com/questions/3948206/json-order-mixed-up) – pdexter Jun 16 '16 at 17:09

2 Answers2

8

You can use toEncoding method which is available since aeson-0.10 (use aeson-0.11 though, if only possible). In that case you have more control over the generated structure:

instance ToJSON Trend where
  toJSON Trend{..} = 
    object [ "Period"    .= period
           , "Africa"    .= africa
           , "Americas"  .= americas
           , "Asia"      .= asia
           ]

  toEncoding Trend {..} =
    pairs $ "Period"   .= period
         <> "Africa"   .= africa
         <> "Americas" .= americas
         <> "Asia"     .= asia 

Or in case as simple is this, is worth using Generic derivation

instance ToJSON Trend where
    toJSON = genericToJSON defaultOptions { fieldLabelModifier = capitaliseFirst }
      where
        capitaliseFirst (x:xs) = toUpper x : xs
        capitaliseFirst []     = []
cdupont
  • 1,138
  • 10
  • 17
phadej
  • 11,947
  • 41
  • 78
  • Thanks, but I am a bit confused. Should I be putting something in the line `enter code here` ? – matt Jun 16 '16 at 18:30
  • Ah, that was something introduced by inlne editor :S – phadej Jun 16 '16 at 18:37
  • do I have to load a particular pragma to use the `toEncoding` ? I get a "series" not in scope and is `<>` specific to aeson? – matt Jun 16 '16 at 18:45
  • Sorry once again, it was `pairs`. `<>` comes from `Data.Monoid` – phadej Jun 16 '16 at 18:53
  • thanks for this. That works a treat and In fact you have managed to save me a lot of head scratching. I am inclined to tick your answer but I feel it might be unfair to the answer above; since my question actually only asks why it was going wrong. Much appreciated! – matt Jun 16 '16 at 18:59
  • proved too helpful not to be chosen ;) – matt Jun 19 '16 at 21:00
  • @phadej, will the `genericToJSON` preserve the order?? – cdupont Dec 10 '18 at 16:35
  • @cdupont no, but `genericToEncoding` will. – phadej Dec 10 '18 at 18:20
  • @phadej thanks... For my use case I need a Value (in which order matters)... How can I obtain a Value from an Encoding? Thanks again – cdupont Dec 10 '18 at 22:33
5

The reason it is happening is because an Aeson Object is just a HashMap, and when aeson converts the HashMap to text it just serializes the key-value pairs in the order that the HashMap returns them - which probably has no relationship to the order in which the keys were inserted.

ErikR
  • 51,541
  • 9
  • 73
  • 124