amitaibu
amitaibu

Reputation: 1116

Inject a list of Json Values into a HashMap

I have a Product referencing a Store model. I'd like the response of /stores/1 to return a JSON that has also the referencing products. Something like:

{
  data: {
    storeName: "Store1",
    id: 1
    products: {
       { productName : "Product1",  productPrice: 10},
       { productName : "Product2",  productPrice: 100},
    }
  }
}

I'm currently stuck on my Yesod handler with getting the products injected in the right place.

getStoreR :: StoreId -> Handler Value
getStoreR storeId = do
    store <- runDB $ get404 storeId

    products <- runDB $ selectList [StoreId ==. storeId] []
    let productsJson = [entityIdToJSON (Entity k r) | Entity k r <- products]

    let storeJson = entityIdToJSON (Entity storeId store)

    -- Inject productsJson under "products" property fails
    let storeJsonWithProducts = HM.insert "products" productsJson storeJson

    return $ object ["data" .= storeJsonWithProducts]

fails with:

Couldn't match expected type ‘HM.HashMap k0 [Value]’
            with actual type ‘Value’
Relevant bindings include
  storeJsonWithProducts :: HM.HashMap k0 [Value]
    (bound at Main.hs:80:9)
In the third argument of ‘HM.insert’, namely ‘storeJson’
In the expression: HM.insert "products" productsJson storeJson

(btw, I have created a single file app with this here)

Upvotes: 1

Views: 144

Answers (1)

dkasak
dkasak

Reputation: 2703

HashMap.insert has the type k -> v -> HashMap k v -> HashMap k v. Your storeJson is not a HashMap but a Value—one made with an Object :: HashMap Text Value -> Value constructor. This means v ~ Value (in case you're not familiar, you can read ~ as type equality). However, this is then a problem since your productsJson is not a Value but actually a [Value].

Therefore, to solve your problem, you need to:

  1. Extract the HashMap from storeJson.

    let storeHM = case storeJson of
                    Object h -> h
    

    You should ensure to handle other constructors here properly, of course, since this will crash if storeJson isn't constructed with Object.

  2. Convert productJson to a Value. One of Value's constructors is Array :: Vector Value -> Value and you can get a Vector Value from [Value] using Data.Vector.fromList:

    import qualified Data.Vector as V
    [...]
    let productsValue = Array (V.fromList productsJson)
    
  3. Finally, insert productsValue into the storeHM HashMap:

    let storeHMWithProducts = HM.insert "products" productsValue storeHM
    

You can then proceed as you did, using object to convert storeHMWithProducts into a JSON Value again:

return $ object ["data" .= storeHMWithProducts]

Upvotes: 1

Related Questions