Search code examples
gojson-api

How to handle jsonapi returning empty struct for struct with false value in golang


It looks like jsonapi.Marshal returns an empty json object for a golang struct with a value that is false. I understand that a struct of zero values is considered zero, but as a caller of this API, am I supposed to just know that empty json object means false?

Here is an example. "Active" is either {"value": true} or {} but never {"value": false}.

package main

import (
    "fmt"
    "log"

    "github.com/DataDog/jsonapi"
    "github.com/golang/protobuf/ptypes/wrappers"
)

type MyResource struct {
    ID     string              `jsonapi:"primary,my-resource"`
    Active *wrappers.BoolValue `jsonapi:"attribute"`
}

func main() {
    // Create an instance of MyResource with Active set to true
    resource := &MyResource{
        ID:     "1",
        Active: &wrappers.BoolValue{Value: true},
    }

    // Marshal the struct into JSON:API format
    data, err := jsonapi.Marshal(resource)
    if err != nil {
        log.Fatalf("Error marshalling resource: %v", err)
    }

    // Print the JSON result
    fmt.Println(string(data))

    // Create an instance of MyResource with Active set to false
    resource2 := &MyResource{
        ID:     "1",
        Active: &wrappers.BoolValue{Value: false},
    }

    // Marshal the struct into JSON:API format
    data2, err := jsonapi.Marshal(resource2)
    if err != nil {
        log.Fatalf("Error marshalling resource: %v", err)
    }

    // Print the JSON result
    fmt.Println(string(data2))
}

// prints the following:
// {"data":{"id":"1","type":"my-resource","attributes":{"Active":{"value":true}}}}
// {"data":{"id":"1","type":"my-resource","attributes":{"Active":{}}}}

Thanks!


Solution

  • This is an (increasingly) common pattern and arises because, Golang scalar types don't have a nil value.

    A solution is to create a helper function to convert e.g. bool to *bool:

    func BoolPtr(b bool) *bool {
        return &b
    }
    

    And then e.g.:

    package main
    
    import (
        "testing"
    
        "github.com/DataDog/jsonapi"
    )
    
    type MyResource struct {
        ID     string `jsonapi:"primary,my-resource"`
        Active *bool  `jsonapi:"attribute"`
    }
    
    func BoolPtr(b bool) *bool {
        return &b
    }
    
    var tests = []struct {
        input *MyResource
        want  string
    }{
        {
            input: &MyResource{
                ID:     "0",
                Active: nil,
            },
            want: `{"data":{"id":"0","type":"my-resource","attributes":{"Active":null}}}`,
        },
        {
            input: &MyResource{
                ID:     "1",
                Active: BoolPtr(true),
            },
            want: `{"data":{"id":"1","type":"my-resource","attributes":{"Active":true}}}`,
        },
        {
            input: &MyResource{
                ID:     "1",
                Active: BoolPtr(false),
            },
            want: `{"data":{"id":"1","type":"my-resource","attributes":{"Active":false}}}`,
        },
    }
    
    func TestMain(t *testing.T) {
        for _, test := range tests {
            t.Run(test.input.ID, func(t *testing.T) {
                b, err := jsonapi.Marshal(test.input)
                if err != nil {
                    t.Errorf("Error marshalling resource: %v", err)
                }
    
                got := string(b)
    
                if got != test.want {
                    t.Errorf("got:  %s\nwant: %s\n", got, test.want)
                }
    
            })
        }
    }
    

    yields:

    === RUN   TestMain
    === RUN   TestMain/0
    === RUN   TestMain/1
    === RUN   TestMain/1#01
    --- PASS: TestMain (0.00s)
        --- PASS: TestMain/0 (0.00s)
        --- PASS: TestMain/1 (0.00s)
        --- PASS: TestMain/1#01 (0.00s)
    PASS
    ok      .../stackoverflow/79343968  0.002s