1

Is there a way to embed a map[string]string inline?

What I got is:

{
    "title": "hello world",
    "body": "this is a hello world post",
    "tags": {
        "hello": "world"
    }
}

What I mean with embed or inline would be an expected result like this:

    {
    "title": "hello world",
    "body": "this is a hello world post",
    "hello": "world"
}

This is my code... I load the information from a yaml file an want to return the JSON in the desired format from above:

This is my yaml:

title: hello world
body: this is a hello world post
tags:
  hello: world

This is my Go code:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"

    "gopkg.in/yaml.v2"
)

type Post struct {
    Title string            `yaml:"title" json:"title"`
    Body  string            `yaml:"body" json:"body"`
    Tags  map[string]string `yaml:"tags" json:",inline"`
}

func main() {

    yamlBytes, err := ioutil.ReadFile("config.yaml")
    if err != nil {
        panic(err)
    }

    var post Post

    yaml.Unmarshal(yamlBytes, &post)

    jsonBytes, err := json.Marshal(post)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(jsonBytes))

}

This obviously does work:

Tags map[string]string `yaml:"tags" json:",inline"`

but I was hoping that something similar exists to embed the tags map without the JSON property tags.

icza
  • 389,944
  • 63
  • 907
  • 827
silverfighter
  • 6,762
  • 10
  • 46
  • 73
  • 1
    How do you want to handle collisions (e.g. a tag named "title")? Is performance a concern? – Peter Apr 18 '18 at 13:46
  • perf is not a concern. It is just a small util. dublicate keys could be overriden – silverfighter Apr 18 '18 at 13:51
  • Related / similar: [Adding Arbitrary fields to json output of an unknown struct](https://stackoverflow.com/questions/42709680/adding-arbitrary-fields-to-json-output-of-an-unknown-struct/42715610#42715610) – icza Apr 18 '18 at 14:25

1 Answers1

5

There is no native way to "flatten" fields, but you can do it manually with a series of Marshal/Unmarshal steps (error handling omitted for brevity):

type Post struct {
    Title string            `yaml:"title" json:"title"`
    Body  string            `yaml:"body" json:"body"`
    Tags  map[string]string `yaml:"tags" json:"-"` // handled by Post.MarshalJSON
}

func (p Post) MarshalJSON() ([]byte, error) {
    // Turn p into a map
    type Post_ Post // prevent recursion
    b, _ := json.Marshal(Post_(p))

    var m map[string]json.RawMessage
    _ = json.Unmarshal(b, &m)

    // Add tags to the map, possibly overriding struct fields
    for k, v := range p.Tags {
        // if overriding struct fields is not acceptable:
        // if _, ok := m[k]; ok { continue }
        b, _ = json.Marshal(v)
        m[k] = b
    }

    return json.Marshal(m)
}

https://play.golang.org/p/Xvu2VYeZFGh

icza
  • 389,944
  • 63
  • 907
  • 827
Peter
  • 29,454
  • 5
  • 48
  • 60
  • 1
    Be careful to remove the space between "json: " and "body" in the struct tag, because right now it is ignored, and the output json contains an uppercase "Body" key. – SirDarius Apr 18 '18 at 14:28
  • @SirDarius Yes, but it's actually copied from the question, and it's already messed up in the question. – icza Apr 18 '18 at 14:32
  • 1
    @icza indeed, but at least let's make sure the answer is perfectly correct :) – SirDarius Apr 18 '18 at 14:34
  • thanks!, I also fixed the spacing around json:"Body" in the question – silverfighter Apr 18 '18 at 14:42