I need to marshal/unmarshal json to struct in golang. Assume the struct is

type A struct {
  Id string `json:"id"`
  Version string `json:"version"`
  Actors []actor `json:"actors`
  Payload struct {
     Name string `json:"name"`
     Number string `json:"number"`
  }
}
type payload struct {
    Name string `json:"name"`
    Number string `json:"number"`
}
type actor struct {
    Id   string `json:"id"`
    Type string `json:"type"`
    Role string `json:"role"`
}

The actors or payload maybe empty. The json maybe

{
  "id": "78a07cea-be2b-499c-b82b-e4f510260484",
  "version": "1.0.0",
  "actors": [
    {
      "id": "1234567",
      "type": "XXX",
      "role": "111"
    },
    {
      "id": "7654321",
      "type": "YYY",
      "role": "222"
    }
  ],
  "payload": ""
}

or

{
  "id": "78a07cea-be2b-499c-b82b-e4f510260484",
  "version": "1.0.0",
  "actors": [],
  "payload": {
       "name": "XXXX",
       "number": "1234567"
   }
}

If i follow the struct A design and try to marshal json with payload empty, i have to init as below

a := A{
  Id: "78a07cea-be2b-499c-b82b-e4f510260484",
  Version: "1.0.0",
  Actors: []actor{
    actor{
      Id: "1234567",
      Type: "XXX",
      Role: "111",
    },
    actor{
      Id: "7654321",
      Type: "YYY",
      Role: "222",
    },
  },
  Payload: payload{},
}

Which will result in below json with one empty payload struct

{
  "id": "78a07cea-be2b-499c-b82b-e4f510260484",
  "version": "1.0.0",
  "actors": [
    {
      "id": "1234567",
      "type": "XXX",
      "role": "111"
    },
    {
      "id": "7654321",
      "type": "YYY",
      "role": "222"
    }
  ],
  "payload": {
     "name":"",
     "number":""
   }
}

Is there any way i can generate

"payload": ""

instead of blank payload struct? Or is there any other struct design for this kind of json format? BTW i cannot pass nil to Payload struct.

2 Answers

3
Peter On Best Solutions

The json.Marshaler interface can be implemented to customize JSON encoding, and the json.Unmarshaler interface for decoding (left as an exercise for the reader):

package main

import (
    "encoding/json"
    "fmt"
)

type A struct {
    Payload payload
}

type payload struct {
    Name   string `json:"name"`
    Number string `json:"number"`
}

func (p payload) MarshalJSON() ([]byte, error) {
    if p.Name == "" && p.Number == "" {
        return []byte(`""`), nil
    }

    type _payload payload // prevent recursion
    return json.Marshal(_payload(p))
}

func main() {
    var a A
    b, _ := json.MarshalIndent(a, "", "  ")
    fmt.Println(string(b))

    a.Payload.Name = "foo"
    b, _ = json.MarshalIndent(a, "", "  ")
    fmt.Println(string(b))
}

// Output:
// {
//   "Payload": ""
// }
// {
//   "Payload": {
//     "name": "foo",
//     "number": ""
//   }
// }

Try it on the playground: https://play.golang.org/p/9jhSWnKTnTf

The ad-hoc _payload type is required to prevent recursion. If one would write return json.Marshal(p), the json package would call MarshalJSON again, because p is of type payload, and payload implements json.Marshaler.

The _payload type has the same underlying type as payload but does not implement json.Marshaler (see Type definitions for details), so it is encoded using the standard rules of the json package; it produces exactly the same output that encoding a value of type payload would produce if payload didn't implement json.Marshaler.

1
Ankit Deshpande On

Check if using omitempty with json struct tag helps. I think it will result in "payload": {} instead of "payload": ""