Search code examples
jsonrestgowebserver

Is there a way to format this json in golang?


I'm just starting to learn GoLang today, I'm trying to build a simple Rest API Web server.

Here's the response Struct I want to send for each request to the web server :

package main

type HttpResp struct{
    Status      int         `json:"status"`
    Description string      `json:"description"`
    Body        string      `json:"body"`
}

And here's my articles.go file who have the function who gets all the articles in the database :

package main

import (
    "encoding/json"
    "net/http"
    "log"
)

type Article struct{
    Id          string  `json:"id"`
    Title       string  `json:"title"`
    Body        string  `json:"body"`
    Description string  `json:"description"`
}

func AllArticles(w http.ResponseWriter, r *http.Request){
    log.Print("/articles - GET")
    db := connect()
    defer db.Close()

    var articles []Article
    results, err := db.Query("SELECT * FROM Articles")

    if err != nil{
        log.Print(err)
        return
    }

    for results.Next(){
        var article Article
        err = results.Scan(&article.Title, &article.Description, &article.Body, &article.Id)

        if err != nil{
            serr, _ := json.Marshal(err)
            json.NewEncoder(w).Encode(HttpResp{Status: 500, Description: "Failed to retrieve all articles", Body: string(serr)})
        }

        articles = append(articles, article)
    }   
    sarr, _ := json.Marshal(articles)

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(HttpResp{Status: 200, Body: string(sarr)})
}

The issue I'm facing here is that the response is like this :

{"status":200,"description":"","body":"[{\"id\":\"1\",\"title\":\"First\",\"body\":\"This is a test body\",\"description\":\"This is a test\"}]"}

I'd like the body to be just JSON and not a string. How can I acheive that ?


Solution

  • No point in marshalling the body separately from the HttpResp. Instead change the Body field's type to interface{} and then set the field to any value of a concrete type as opposed to a json string, e.g. []Article and then marshal the resp once.

    type HttpResp struct{
        Status      int         `json:"status"`
        Description string      `json:"description"`
        Body        interface{} `json:"body"`
    }
    

    And the rest...

    package main
    
    import (
        "encoding/json"
        "net/http"
        "log"
    )
    
    type Article struct{
        Id          string  `json:"id"`
        Title       string  `json:"title"`
        Body        string  `json:"body"`
        Description string  `json:"description"`
    }
    
    func AllArticles(w http.ResponseWriter, r *http.Request){
        log.Print("/articles - GET")
        db := connect()
        defer db.Close()
    
        var articles []Article
        results, err := db.Query("SELECT * FROM Articles")
    
        if err != nil{
            log.Print(err)
            return
        }
    
        for results.Next(){
            var article Article
            err = results.Scan(&article.Title, &article.Description, &article.Body, &article.Id)
    
            if err != nil{
                serr, _ := json.Marshal(err)
                json.NewEncoder(w).Encode(HttpResp{Status: 500, Description: "Failed to retrieve all articles", Body: string(serr)})
            }
    
            articles = append(articles, article)
        }
    
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(HttpResp{Status: 200, Body: articles})
    }