1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
------------------------------------------------------------------------------
-- | Author:  Jonas Oberschweiber
--   Version: February 2025
--
-- This library contains the definition of a data type to represent
-- JSON values.
--
------------------------------------------------------------------------------

module JSON.Data
  ( JValue(..)
  , JObject, fromJObject, toJObject, lookupName, insertField
  ) where

import Data.List ( nubBy, partition )

-- | Abstract representation of a JSON value.
data JValue =
    JBool   Bool    -- ^ a Boolean value (`true` or `false` in JSON)                
  | JNull           -- ^ null, i.e. a missing value                                 
  | JString String  -- ^ a JSON string
  | JInt    Int     -- ^ a JSON number without decimal point and exponent           
  | JNumber Float   -- ^ a JSON number (numbers are always floats in JSON)          
  | JArray [JValue] -- ^ a JSON array, represented by a list of JValues             
  | JObject JObject -- ^ a JSON object, represented by a map from Strings to JValues
  deriving (Eq, Show)

-- | A JSON object is just some representation of a mapping from names
--   (strings) to JSON values.
--   It is an abstract type (rather than an explicit list) to ensure
--   that there are no duplicate entries.
newtype JObject = JSONObject [(String, JValue)]
  deriving Show

-- JSON objects are equivalent if they contain the same name/value pairs
-- possibly in a different order.
instance Eq JObject where
  JSONObject jo1 == JSONObject jo2 = eqJObject jo1 jo2
   where
    eqJObject []          []    = True
    eqJObject []          (_:_) = False
    eqJObject ((k,v):kvs) jo    =
      let (eqk,neqk) = partition ((==k) . fst) jo
      in case eqk of [(_,v1)] -> v==v1 && eqJObject kvs neqk
                     _        -> False

-- | Extracts the list of name / JSON value pairs from a JSON object.
fromJObject :: JObject -> [(String, JValue)]
fromJObject (JSONObject jo) = jo

-- | Transforms a list of name / JSON value pairs into a JSON object.
--   Pairs with duplicated names are deleted to ensure that the JSON object
--   is a map from names to values.
toJObject :: [(String,JValue)] -> JObject
toJObject = JSONObject . nubBy (\(k1, _) (k2, _) -> k1 == k2)

-- | Retrieves the JSON value with a given name from a JSON object if it exists.
lookupName :: String -> JObject -> Maybe JValue
lookupName name (JSONObject jo) = lookup name jo

-- | Inserts a name / JSON value pair in a JSON object.
--   If the name already exists, the existing value is overwritten.
insertField :: String -> JValue -> JObject -> JObject
insertField name val (JSONObject jo) = JSONObject (insert jo)
 where
  insert []                     = [(name,val)]
  insert ((k,v):fs) | k == name = (name,val) : fs
                    | otherwise = (k,v) : insert fs

------------------------------------------------------------------------------