BeeGee
BeeGee

Reputation: 875

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!

Upvotes: 0

Views: 56

Answers (1)

DazWilkin
DazWilkin

Reputation: 40296

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

Upvotes: 1

Related Questions