1

I have the following structures to export onto json:

type ExportedIncident struct {
    Title      string `json:"title"`
    Host       string `json:"host"`
    Status     string `json:"status"`
    Date       string `json:"date"`
    Notes      []ExportedNote `json:"notes"`
    LogEntries []ExportedLogEntry `json:"log_entries"`
}

And I want underscore cased fields, so I had to define each field just for that as described in this answer: https://stackoverflow.com/a/11694255/1731473

But it's really cumbersome and I believe there is a simpler solution in Go, but I can't find it.

How can I set default letter-case (underscore, snake, camel...) for JSON export?

Soullivaneuh
  • 3,553
  • 3
  • 37
  • 71
  • 1
    What is a "default case for JSON export"? – icza Mar 20 '19 at 10:30
  • 1
    @icza I believe they're talking about the letter case, ie CamelCase vs snake_case. – mkopriva Mar 20 '19 at 10:35
  • 3
    There isn't a simpler solution. You have to use struct tags. – icza Mar 20 '19 at 10:38
  • 1
    @Soullivaneuh The `encoding/json` package doesn't provide the option the set the default letter-case. The best you could do to avoid writing the tags by hand is to write a program that manipulates the source code and generates the tags for you. The set of packages under `go/`, e.g. `go/ast`, `go/types` can help you with this. – mkopriva Mar 20 '19 at 10:41
  • Yes, I mean camel case, snake etc... I'm not sure that manipulate the source code will be less cumbersome than writing the tags. I'm quite surprised that a recent language like go does not provide this kind of possibility. Do you know if an issue is opened about this? – Soullivaneuh Mar 20 '19 at 10:45
  • 1
    I'm not sure whether there are or aren't open issues regarding an option that allows to set the way the field names are marshaled by default, but I wouldn't hold my breath if I were you. I know, however, that there do exist 3rd party json packages and maybe they do provide the option you desire, but as I've personally never used these packages you'll have to do the search yourself. Another option would be to fork `encoding/json`, modify it, and use that fork instead of the std lib, but this clearly has its own downsides. – mkopriva Mar 20 '19 at 10:54
  • 1
    A custom JSON marshaler could achieve this. It's non-trivial to implement, but very doable, since all you are doing is changing the default (unnamed) tag-name retrieval. – colm.anseo Mar 20 '19 at 13:01
  • I would indeed avoid to rely on a fork just for that. :-) – Soullivaneuh Mar 20 '19 at 13:19
  • "I'm quite surprised that a recent language like go does not provide this kind of possibility" - take a look at the reasoning and purpose behind the creation of the language. A key focus on Go is on simplicity and minimal feature set. Catering to every edge case is directly opposed to the goals of the language. – Adrian Mar 20 '19 at 13:41
  • 1
    Yes, but I especially don't see the "simplicity" of re-defining each struct field just because we can't set a case option. There is maybe some reason but I can't figure out now. – Soullivaneuh Mar 20 '19 at 14:08

2 Answers2

1

Unfortunately there are no opportunities to export your fields into snake_case so you have to maintain tags by yourself.

Technically you can use method MarshalJSON and perform all manipulations inside this method, but it isn't easier way...

cn007b
  • 16,596
  • 7
  • 59
  • 74
  • 1
    A custom JSON marshaler could achieve what the OP requires. – colm.anseo Mar 20 '19 at 11:52
  • 3
    @colminator: That doesn't sound _easier_ than maintaining tags, though :P – Jonathan Hall Mar 20 '19 at 13:01
  • @Flimzy correct. But this answer uses the language "there are no opportunities to export" in the format the OP requested. And that statement is clearly false. – colm.anseo Mar 20 '19 at 13:06
  • 1
    The question is clearly about the standard library's `json` package. Answering within that context should not require additional qualifiers. – Jonathan Hall Mar 20 '19 at 13:07
  • 2
    I like to keep people's options open. When I see "you can't do that" - I just think "Challenge Accepted!" – colm.anseo Mar 20 '19 at 13:10
  • 1
    I'm not against using an external library for that if necessary, even if it would be better to have this under the root one. – Soullivaneuh Mar 20 '19 at 13:18
  • 1
    @Soullivaneuh there are some recent additions to the json package even in the last year e.g. `json.Decoder.DisallowUnknownFields` - which add new functionality without breaking backward compatibility. So in theory, the go team could add a `json.Encoder.CamelCaseTags`. – colm.anseo Mar 20 '19 at 13:24
  • 1
    I'll accept the answer as it indeed match the precise question: Can I do this with the native package? And indeed, the proposal was made and refused: https://github.com/golang/go/issues/23027 I don't understand why it's refused, but right. – Soullivaneuh Mar 20 '19 at 13:26
  • @colminator Look at the issue link. Do you think I should try a new one? – Soullivaneuh Mar 20 '19 at 13:27
  • Consider the return on investment. I've written marshalers/unmarshalers. You will learn *A LOT* - and more about the reflect pkg than you'd ever care to know. I don't regret it. But was the effort worth it, for the problem you are trying to solve - that is what you have to ask yourself. – colm.anseo Mar 20 '19 at 13:31
1

As mentioned by @Vladimir Kovpak, you can't do this with the standard library, at least at the moment.


Though, inspired by this, you can achieve something close to what you want to do. Check out MarshalIndentSnakeCase:

func MarshalIndentSnakeCase(v interface{}, prefix, indent string) ([]byte, error) {
    b, err := json.MarshalIndent(v, prefix, indent)
    if err != nil {
        return nil, err
    }

    x := convertKeys(b) // Here convert all keys from CamelCase to snake_case

    buf := &bytes.Buffer{}
    err = json.Indent(buf, []byte(x), prefix, indent)
    if err != nil {
        return nil, err
    }

    return buf.Bytes(), nil
}

Down Sides:

  • You have to do the same in the opposite direction for Unmashalling to work.
  • The order of elements is lost due to using a map inside convertKeys().

Try it on Go Playground.

Community
  • 1
  • 1
ifnotak
  • 4,147
  • 3
  • 22
  • 36