irmorteza
irmorteza

Reputation: 1654

How to omit conditional field of struct within marshal

There is struct of MyStruct.

type MyStruct struct {
    Code        int   `json:"Code"`
    Flags       uint8 `json:"Flags"`
    OptionField int   `json:",omitempty"`
}

Following code convert it to json.

f := MyStruct{Code:500, OptionField:41}
r, _ := json.Marshal(f)
fmt.Println(string(r)

I need to "OptionField" be optional. Some time it should exist in json with one of values [0, 1, 2, 3, ]. and in the other time it should exclude from json.

My problem is: omitempty will exclude it when the value is zero, and the default value of int is zero. Is there any way to omit field in condition (ex: omit if value is -1). Or there is any way to do it.

Upvotes: 3

Views: 1697

Answers (2)

Benno
Benno

Reputation: 318

You can use some custom struct tags to filter out what you need into a map before marshaling to json. Using pointers for this can get messy fast besides being prone to bugs if each value is not carefully dereferenced.

I would recommend refactoring the simplified example to include a MapOptions struct that handles getting the tag for each available option so you can quickly enumerate and update all available map options instead of hard-coding the tag name as shown.

package main

import (
    "encoding/json"
    "fmt"

    "github.com/fatih/structs"
)

func main() {
    myStruct := MyStruct{
        Code:        500,
        Flags:       10,
        OptionField: 0,
    }

    json, _ := myStruct.ToJson("omitoptions")
    fmt.Printf("%s\n", json) // {"Code":500,"Flags":10}

    json, _ = myStruct.ToJson("omitempty")
    fmt.Printf("%s\n", json) // {"Code":500,"Flags":10}

    // entering an invalid tag, or no tag at all, defaults to "structs"
    json, _ = myStruct.ToJson("")
    fmt.Printf("%s\n", json) // {"Code":500,"Flags":10,"OptionField":0}
}

type MyStruct struct {
    Code        int   `structs:"Code" omitoptions:"Code" omitempty:"Code,omitempty"`
    Flags       uint8 `structs:"Flags" omitoptions:"Flags" omitempty:"Flags,omitempty"`
    OptionField int   `structs:"OptionField" omitoptions:"-" omitempty:"OptionField,omitempty"`
}

// ToMap converts the struct to a map, using the selected tag name to filter fields
func (c *MyStruct) ToMap(tag string) map[string]interface{} {
    s := structs.New(c)
    s.TagName = tag

    return s.Map()
}

// ToJson serializes the struct to a JSON byte slice, using the selected tag name
func (c *MyStruct) ToJson(tag string) ([]byte, error) {
    m := c.ToMap(tag)
    bytes, err := json.Marshal(m)
    if err != nil {
        return nil, err
    }

    return bytes, nil
}

Upvotes: 0

Eternal_flame-AD
Eternal_flame-AD

Reputation: 342

You could use *int instead of int and set the pointer value to nil in order to omit this.

package main

import (
    "encoding/json"
    "fmt"
)

type MyStruct struct {
    Code        int   `json:"Code"`
    Flags       uint8 `json:"Flags"`
    OptionField *int  `json:",omitempty"`
}

func format(s MyStruct) string {
    r, _ := json.Marshal(s)
    return string(r)
}

func main() {
    f := MyStruct{Code: 500, Flags: 10, OptionField: new(int)}
    fmt.Println(format(f)) // {"Code":500,"Flags":10,"OptionField":0}
    f.OptionField = nil
    fmt.Println(format(f)) // {"Code":500,"Flags":10}
}

Upvotes: 6

Related Questions