Looping/iterate over the second level nested JSON in go lang - json

Consider the following code:
package main
import (
"encoding/json"
"fmt"
"reflect"
)
func main() {
//Creating the maps for JSON
m := map[string]interface{}{}
//Parsing/Unmarshalling JSON encoding/json
err := json.Unmarshal([]byte(input), &m)
fmt.Println("\nReflect type of Parsing/Unmarshalling Error Object:\n",reflect.TypeOf(err))
fmt.Println("\nParsing/Unmarshalling Error Object:\n",err)
if err != nil {
panic(err)
}
fmt.Println("\nParsed JSON is as follows:\n",m)
fmt.Println("\nReflect type of parsed json object:\n", reflect.TypeOf(m))
for firstLvlkey, firstLvlValue := range m {
fmt.Println("First Level Key:", firstLvlkey)
fmt.Println("First Level Key reflect type of :", reflect.TypeOf(firstLvlkey))
fmt.Println("First Level Value:", firstLvlValue)
fmt.Println("First Level Value reflect type of :", reflect.TypeOf(firstLvlValue))
// <===============================>
//Here I want to iterate/loop over innerJSON1, InnerJSON2 then reach to level InnerInnerJSONArray - fld1 and fld2
// <===============================>
}
}
const input = `
{
"outterJSON":{
"innerJSON1":{
"value1":10,
"value2":22
,
"InnerInnerArray": [ "test1" , "test2"],
"InnerInnerJSONArray": [ {"fld1" : "val1"} , {"fld2" : "val2"} ]
},
"InnerJSON2":"NoneValue"
}
}
`
I have some requirement like I want to read/get all the Key and value in String type for some processing adn I can't define the struct because I will be getting dynamic JSON input (e.g InnerInnerArray as a string then second level loop will give me index of array and process each JSON having key fld1 and val1).
I wish to iterate over every key/value pair contained within it, what is the most efficient way of going through the map?
Note: I am Newbie for Go-lang, your suggestion/improvement on question is also most welcome.

See this blog entry which thoroughly covers this subject, specifically the section Decoding arbitrary data. Using that you can do something like this:
(playground example)
package main
import (
"encoding/json"
"fmt"
)
func main() {
// Creating the maps for JSON
m := map[string]interface{}{}
// Parsing/Unmarshalling JSON encoding/json
err := json.Unmarshal([]byte(input), &m)
if err != nil {
panic(err)
}
parseMap(m)
}
func parseMap(aMap map[string]interface{}) {
for key, val := range aMap {
switch concreteVal := val.(type) {
case map[string]interface{}:
fmt.Println(key)
parseMap(val.(map[string]interface{}))
case []interface{}:
fmt.Println(key)
parseArray(val.([]interface{}))
default:
fmt.Println(key, ":", concreteVal)
}
}
}
func parseArray(anArray []interface{}) {
for i, val := range anArray {
switch concreteVal := val.(type) {
case map[string]interface{}:
fmt.Println("Index:", i)
parseMap(val.(map[string]interface{}))
case []interface{}:
fmt.Println("Index:", i)
parseArray(val.([]interface{}))
default:
fmt.Println("Index", i, ":", concreteVal)
}
}
}
const input = `
{
"outterJSON": {
"innerJSON1": {
"value1": 10,
"value2": 22,
"InnerInnerArray": [ "test1" , "test2"],
"InnerInnerJSONArray": [{"fld1" : "val1"} , {"fld2" : "val2"}]
},
"InnerJSON2":"NoneValue"
}
}
`
This will print:
//outterJSON
//innerJSON1
//InnerInnerJSONArray
//Index: 0
//fld1 : val1
//Index: 1
//fld2 : val2
//value1 : 10
//value2 : 22
//InnerInnerArray
//Index 0 : test1
//Index 1 : test2
//InnerJSON2 : NoneValue
The key thing is that you have to use type assertion when working with interface types. The type switch makes it easy to determine the type as needed. The code will recursively range through any nested array or map so you can add as many levels as you wish and get all your values.

There are related questions here and here (and possibly others).
There are some more sophisticated JSON parsing APIs that make your job easier. An example is stretchr/objx.
An example of using objx:
document, err := objx.FromJSON(json)
// TODO handle err
document.Get("path.to.field[0].you.want").Str()
This works when you really don't know what the JSON structure will be. However, if you do know the structure of your JSON input ahead of time, the preferred way is to describe it with structs and use the standard API for marshalling.

You'll need to parse the JSON and then recurse through the structure inspecting the types of the contained values and handling them in some way.
The example function below takes an *interface{} (pointer to any type) and a handler function of string, int, and object pointers to which it yields the items it discovers:
func eachJsonValue(obj *interface{}, handler func(*string, *int, *interface{})) {
if obj == nil {
return
}
// Yield all key/value pairs for objects.
o, isObject := (*obj).(map[string]interface{})
if isObject {
for k, v := range o {
handler(&k, nil, &v)
eachJsonValue(&v, handler)
}
}
// Yield each index/value for arrays.
a, isArray := (*obj).([]interface{})
if isArray {
for i, x := range a {
handler(nil, &i, &x)
eachJsonValue(&x, handler)
}
}
// Do nothing for primitives since the handler got them.
}
Calling it as demonstrated below will print the listed results. Your handler function could, of course, do something special with known key/values such as "fld1":
func main() {
// Parse the JSON.
var obj interface{}
json.Unmarshal([]byte(input), &obj) // XXX: check the error value.
// Handle object key/value pairs and array index/items.
eachJsonValue(&obj, func(key *string, index *int, value *interface{}) {
if key != nil { // It's an object key/value pair...
fmt.Printf("OBJ: key=%q, value=%#v\n", *key, *value)
} else { // It's an array item...
fmt.Printf("ARR: index=%d, value=%#v\n", *index, *value)
}
})
}
// OBJ: key="outterJSON", value=map[string]interface {}{...}
// OBJ: key="innerJSON1", value=map[string]interface {}{...}
// OBJ: key="value1", value=10
// OBJ: key="value2", value=22
// OBJ: key="InnerInnerArray", value=[]interface {}{...}
// ARR: index=0, value="test1"
// ARR: index=1, value="test2"
// OBJ: key="InnerInnerJSONArray", value=[]interface {}{...}
// ARR: index=0, value=map[string]interface {}{...}
// OBJ: key="fld1", value="val1"
// ARR: index=1, value=map[string]interface {}{...}
// OBJ: key="fld2", value="val2"
// OBJ: key="InnerJSON2", value="NoneValue"

I have implemented something very similar to https://stackoverflow.com/users/1078890/iamnan solution. The body of parseMap and parseArray is combined into one and it looks something like this.
func printJson(res1 map[string]interface{}, res2 []interface{}) {
for k, v := range res1 {
switch vv := v.(type) {
case float64, int, string:
fmt.Println(k, ":", vv)
case []interface{}:
fmt.Println(k, ":")
printJson(nil, vv)
case map[string]interface{}:
fmt.Println(k, ":")
printJson(vv, nil)
default:
fmt.Println(k, ":", vv)
}
}
for k, v := range res2 {
switch vv := v.(type) {
case float64, int, string:
fmt.Println(k, ":", vv)
case []interface{}:
fmt.Println(k, ":")
printJson(nil, vv)
case map[string]interface{}:
fmt.Println(k, ":")
printJson(vv, nil)
default:
fmt.Println(k, ":", vv)
}
}
}
Code can be found here https://gist.github.com/sandeep-sarkar/78a0e96461b4dec727386a96404d29b0

Related

Type switch to []map[string]interface{} on the value of map[string]interface{}

Problem
I'm facing the issue to remove not required arrays from an json object eg. arrays with only one element which is not an object or array. (No arrays as root of the input)
Example
In:
{"name": [{ "inner": ["test"] }]}
Wanted Out:
{"name": [{ "inner": "test" }]}
Approach
I started with a simple type switch on the values of a parsed map[string]interface{} and recognized that it wont switch to the case []map[string]interface{}. (Given example)
So here is the implementation I came up with. It works for most of the scenarios but not for inner Objects within an array yet.
type jsonMap map[string]interface{}
type jsonMapList []map[string]interface{}
m := jsonMap{}
err := json.Unmarshal(s, &m)
if err != nil {
panic(err)
}
res := removeFromObject(m)
bytes, err := json.Marshal(res)
if err != nil {
panic(err)
}
result := string(bytes)
log.Infof("Parse Result: %s", result)
func removeFromObject(in jsonMap) jsonMap {
res := jsonMap{}
for k, v := range in {
switch value := v.(type) {
case jsonMap:
res[k] = removeFromObject(value)
case jsonMapList:
list := []jsonMap{}
for _, entry := range value {
list = append(list, removeFromObject(entry))
}
res[k] = list
case []interface{}:
if len(value) == 1 {
res[k] = value[0]
} else {
res[k] = value
}
default:
res[k] = value
}
}
return res
}
Question
How do I switch case to an object array, so that I can recursively resolve the objects within that array too?
You can use this function to remove the undesired arrays.
func removearr(x interface{}) interface{} {
switch val := x.(type) {
case map[string]interface{}:
for k, v := range val {
val[k] = removearr(v)
}
return val
case []interface{}:
if len(val) == 1 {
// remove array only if the value is not an object
if _, ok := val[0].(map[string]interface{}); !ok {
return removearr(val[0])
}
}
for i, v := range val {
val[i] = removearr(v)
}
return val
default:
return x
}
}
https://play.golang.com/p/mwo7Y2rJ_lc

Need to parse integers in JSON as integers, not floats

First of all let me explain the problem.
I have a stream of JSON records coming into my Golang app. It basically forwards these to a data store (InfluxDB). There are some integer values in the JSON, and also some float values. It is essential that these get forwarded to the data store with the original data type. If they don't, there will be type conflicts and the write operation will fail.
The Ruby JSON parser has no trouble doing this:
require 'json'
obj = { "a" => 123, "b" => 12.3 }
parsed = JSON.parse(obj.to_json)
print parsed["a"].class # => Integer
print parsed["b"].class # => Float
The encoding/json package in Golang, does have some trouble (all numbers are parsed as floats):
package main
import "encoding/json"
import "fmt"
func main () {
str := "{\"a\":123,\"b\":12.3}"
var parsed map[string]interface{}
json.Unmarshal([]byte(str), &parsed)
for key, val := range parsed {
switch val.(type) {
case int:
fmt.Println("int type: ", key)
case float64:
fmt.Println("float type: ", key)
default:
fmt.Println("unknown type: ", key)
}
}
}
Which prints:
float type: a
float type: b
I need a way to parse ints as ints, and floats as floats, in the way the Ruby JSON parser does.
It is not feasible in this case to parse everything as strings and check whether or not there is a decimal. Certain values come in as strings such as "123" and I need to push those as strings.
I do not have structs for the parsed objects, nor is that an option. The golang app doesn't actually care about the schema and just forwards the input as it receives it.
I tried the approach outlined here: Other ways of verifying reflect.Type for int and float64 (using reflect) but it did not accurately identify the int:
t := reflect.TypeOf(parsed["a"])
k := t.Kind()
k == reflect.Int // false
k == reflect.Float64 // true
For example, Ruby JSON number types using the general Go mechanism for custom JSON values,
package main
import (
"encoding/json"
"fmt"
"strconv"
)
func main() {
str := `{"a":123,"b":12.3,"c":"123","d":"12.3","e":true}`
var raw map[string]json.RawMessage
err := json.Unmarshal([]byte(str), &raw)
if err != nil {
panic(err)
}
parsed := make(map[string]interface{}, len(raw))
for key, val := range raw {
s := string(val)
i, err := strconv.ParseInt(s, 10, 64)
if err == nil {
parsed[key] = i
continue
}
f, err := strconv.ParseFloat(s, 64)
if err == nil {
parsed[key] = f
continue
}
var v interface{}
err = json.Unmarshal(val, &v)
if err == nil {
parsed[key] = v
continue
}
parsed[key] = val
}
for key, val := range parsed {
fmt.Printf("%T: %v %v\n", val, key, val)
}
}
Playground: https://play.golang.org/p/VmG8IZV4CG_Y
Output:
int64: a 123
float64: b 12.3
string: c 123
string: d 12.3
bool: e true
Another example, Ruby JSON number types using the Go json.Number type,
package main
import (
"encoding/json"
"fmt"
"strings"
)
func main() {
str := `{"a":123,"b":12.3,"c":"123","d":"12.3","e":true}`
var parsed map[string]interface{}
d := json.NewDecoder(strings.NewReader(str))
d.UseNumber()
err := d.Decode(&parsed)
if err != nil {
panic(err)
}
for key, val := range parsed {
n, ok := val.(json.Number)
if !ok {
continue
}
if i, err := n.Int64(); err == nil {
parsed[key] = i
continue
}
if f, err := n.Float64(); err == nil {
parsed[key] = f
continue
}
}
for key, val := range parsed {
fmt.Printf("%T: %v %v\n", val, key, val)
}
}
Playground: https://play.golang.org/p/Hk_Wb0EM-aY
Output:
int64: a 123
float64: b 12.3
string: c 123
string: d 12.3
bool: e true
A working version of #ShudiptaSharma's suggestion.
There exists a type json.Number in json package which has 3 format functions String() string, Float64() (float64, error) and Int64() (int64, error). We can use them to parse integer and float type.
So, I handle json integer parsing in this way:
package main
import "encoding/json"
import "fmt"
import "strings"
import (
"reflect"
)
func main () {
str := "{\"a\":123,\"b\":12.3}"
var parsed map[string]interface{}
d := json.NewDecoder(strings.NewReader(str))
d.UseNumber()
fmt.Println(d.Decode(&parsed))
for key, val := range parsed {
fmt.Println(reflect.TypeOf(val))
fmt.Printf("decoded to %#v\n", val)
switch val.(type) {
case json.Number:
if n, err := val.(json.Number).Int64(); err == nil {
fmt.Println("int64 type: ", key, n)
} else if f, err := val.(json.Number).Float64(); err == nil {
fmt.Println("float64 type: ", key, f)
} else {
fmt.Println("string type: ", key, val)
}
default:
fmt.Println("unknown type: ", key, val)
}
fmt.Println("===============")
}
}

How to check if a json matches a struct / struct fields

Is there an easy way to check if each field of myStruct was mapped by using json.Unmarshal(jsonData, &myStruct).
The only way I could image is to define each field of a struct as pointer, otherwise you will always get back an initialized struct.
So every jsonString that is an object (even an empty one {}) will return an initialized struct and you cannot tell if the json represented your struct.
The only solution I could think of is quite uncomfortable:
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name *string `json:name`
Age *int `json:age`
Male *bool `json:male`
}
func main() {
var p *Person
err := json.Unmarshal([]byte("{}"), &p)
// handle parse error
if err != nil {
return
}
// handle json did not match error
if p.Name == nil || p.Age == nil || p.Male == nil {
return
}
// now use the fields with dereferencing and hope you did not forget a nil check
fmt.Println("Hello " + *p.Name)
}
Maybe one could use a library like govalidator and use SetFieldsRequiredByDefault. But then you still have to execute the validation and still you are left with the whole pointer dereferencing for value retrieval and the risk of nil pointer.
What I would like is a function that returns my unmarshaled json as a struct or an error if the fields did not match. The only thing the golang json library offers is an option to fail on unknown fields but not to fail on missing fields.
Any idea?
Another way would be to implement your own json.Unmarshaler which uses reflection (similar to the default json unmarshaler):
There are a few points to consider:
if speed is of great importance to you then you should write a benchmark to see how big the impact of the extra reflection is. I suspect its negligible but it can't hurt to write a small go benchmark to get some numbers.
the stdlib will unmarshal all numbers in your json input into floats. So if you use reflection to set integer fields then you need to provide the corresponding conversion yourself (see TODO in example below)
the json.Decoder.DisallowUnknownFields function will not work as expected with your type. You need to implement this yourself (see example below)
if you decide to take this approach you will make your code more complex and thus harder to understand and maintain. Are you actually sure you must know if fields are omitted? Maybe you can refactor your fields to make good usage of the zero values?
Here a fully executable test of this approach:
package sandbox
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
)
type Person struct {
Name string
City string
}
func (p *Person) UnmarshalJSON(data []byte) error {
var m map[string]interface{}
err := json.Unmarshal(data, &m)
if err != nil {
return err
}
v := reflect.ValueOf(p).Elem()
t := v.Type()
var missing []string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
val, ok := m[field.Name]
delete(m, field.Name)
if !ok {
missing = append(missing, field.Name)
continue
}
switch field.Type.Kind() {
// TODO: if the field is an integer you need to transform the val from float
default:
v.Field(i).Set(reflect.ValueOf(val))
}
}
if len(missing) > 0 {
return errors.New("missing fields: " + strings.Join(missing, ", "))
}
if len(m) > 0 {
extra := make([]string, 0, len(m))
for field := range m {
extra = append(extra, field)
}
// TODO: consider sorting the output to get deterministic errors:
// sort.Strings(extra)
return errors.New("unknown fields: " + strings.Join(extra, ", "))
}
return nil
}
func TestJSONDecoder(t *testing.T) {
cases := map[string]struct {
in string
err string
expected Person
}{
"Empty object": {
in: `{}`,
err: "missing fields: Name, City",
expected: Person{},
},
"Name missing": {
in: `{"City": "Berlin"}`,
err: "missing fields: Name",
expected: Person{City: "Berlin"},
},
"Age missing": {
in: `{"Name": "Friedrich"}`,
err: "missing fields: City",
expected: Person{Name: "Friedrich"},
},
"Unknown field": {
in: `{"Name": "Friedrich", "City": "Berlin", "Test": true}`,
err: "unknown fields: Test",
expected: Person{Name: "Friedrich", City: "Berlin"},
},
"OK": {
in: `{"Name": "Friedrich", "City": "Berlin"}`,
expected: Person{Name: "Friedrich", City: "Berlin"},
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
var actual Person
r := strings.NewReader(c.in)
err := json.NewDecoder(r).Decode(&actual)
switch {
case err != nil && c.err == "":
t.Errorf("Expected no error but go %v", err)
case err == nil && c.err != "":
t.Errorf("Did not return expected error %v", c.err)
case err != nil && err.Error() != c.err:
t.Errorf("Expected error %q but got %v", c.err, err)
}
if !reflect.DeepEqual(c.expected, actual) {
t.Errorf("\nWant: %+v\nGot: %+v", c.expected, actual)
}
})
}
}
You could compare p with a empty struct, instead of comparing each field with nil.
// handle json did not match error
if p == Person{} {
return
}
Since Person{} will initialize with the 0 value of each field, this will result in each property that is pointers to be nil, strings will be "", ints will be 0, and so on.

Check if strings are JSON format

How to check if a given string is in form of multiple json string separated by spaces/newline?
For example,
given: "test" 123 {"Name": "mike"} (3 json concatenated with space)
return: true, since each of item ("test" 123 and {"Name": "mike"}) is a valid json.
In Go, I can write a O(N^2) function like:
// check given string is json or multiple json concatenated with space/newline
func validateJSON(str string) error {
// only one json string
if isJSON(str) {
return nil
}
// multiple json string concatenate with spaces
str = strings.TrimSpace(str)
arr := []rune(str)
start := 0
end := 0
for start < len(str) {
for end < len(str) && !unicode.IsSpace(arr[end]) {
end++
}
substr := str[start:end]
if isJSON(substr) {
for end < len(str) && unicode.IsSpace(arr[end]) {
end++
}
start = end
} else {
if end == len(str) {
return errors.New("error when parsing input: " + substr)
}
for end < len(str) && unicode.IsSpace(arr[end]) {
end++
}
}
}
return nil
}
func isJSON(str string) bool {
var js json.RawMessage
return json.Unmarshal([]byte(str), &js) == nil
}
But this won't work for large input.
There are two options. The simplest, from a coding standpoint, is going to be just to decode the JSON string normally. You can make this most efficient by decoding to an empty struct:
package main
import "encoding/json"
func main() {
input := []byte(`{"a":"b", "c": 123}`)
var x struct{}
if err := json.Unmarshal(input, &x); err != nil {
panic(err)
}
input = []byte(`{"a":"b", "c": 123}xxx`) // This one fails
if err := json.Unmarshal(input, &x); err != nil {
panic(err)
}
}
(playground link)
This method has a few potential drawbacks:
It only works with a single JSON object. That is, a list of objects (as requested in the question) will fail, without additional logic.
As pointed out by #icza in comments, it only works with JSON objects, so bare arrays, numbers, or strings will fail. To accomodate these types, interface{} must be used, which introduces the potential for some serious performance penalties.
The throw-away x value must still be allocated, and at least one reflection call is likely under the sheets, which may introduce a noticeable performance penalty for some workloads.
Given these limitations, my recommendation is to use the second option: loop through the entire JSON input, ignoring the actual contents. This is made simple with the standard library json.Decoder:
package main
import (
"bytes"
"encoding/json"
"io"
)
func main() {
input := []byte(`{"a":"b", "c": 123}`)
dec := json.NewDecoder(bytes.NewReader(input))
for {
_, err := dec.Token()
if err == io.EOF {
break // End of input, valid JSON
}
if err != nil {
panic(err) // Invalid input
}
}
input = []byte(`{"a":"b", "c": 123}xxx`) // This input fails
dec = json.NewDecoder(bytes.NewReader(input))
for {
_, err := dec.Token()
if err == io.EOF {
break // End of input, valid JSON
}
if err != nil {
panic(err) // Invalid input
}
}
}
(playground link)
As Volker mentioned in the comments, use a *json.Decoder to decode all json documents in your input successively:
package main
import (
"encoding/json"
"io"
"log"
"strings"
)
func main() {
input := `"test" 123 {"Name": "mike"}`
dec := json.NewDecoder(strings.NewReader(input))
for {
var x json.RawMessage
switch err := dec.Decode(&x); err {
case nil:
// not done yet
case io.EOF:
return // success
default:
log.Fatal(err)
}
}
}
Try it on the playground: https://play.golang.org/p/1OKOii9mRHn
Try fastjson.Scanner:
s := `"test" 123 {"Name": "mike"}`
var sc fastjson.Scanner
sc.Init(s)
// Iterate over a stream of json objects
for sc.Next() {}
if sc.Error() != nil {
fmt.Println("ok")
} else {
fmt.Println("false")
}

create a map of string from a JSON with unknow key-values in Go

I try to create a map of strings from a JSON with an undefined number of unknow key-values.
Here is my example JSON file:
{
"localhost":
{
"tag": "dev_latest",
"vhost": "localhost.com"
},
"development":
{
"tag": "dev_latest",
"vhost": "dev.com"
}
}
I want to create a map[string]string with value like this:
config := map[string]string{
"localhost-tag": "dev_latest",
"localhost-vhost": "localhost.com,
"development-tag": "dev_latest,
...
}
To parse a JSON with "github.com/jmoiron/jsonq" with known values, is quite easy, but in this case, localhost can be anything and tag can be any other thing.
My entry point in my Go code is like this:
func ParseJson(){
configPath := GetConfigPath()
b, err := ioutil.ReadFile(configPath)
// Here, I need to create my map of strings..
return configKeyStr
}
Any help will be really appreciate.
Thanks!
Easy to do. Simply convert.
package main
import (
"encoding/json"
"fmt"
"log"
)
const s = `
{
"localhost":
{
"tag": "dev_latest",
"vhost": "localhost.com"
},
"development":
{
"tag": "dev_latest",
"vhost": "dev.com"
}
}
`
func main() {
var m map[string]interface{}
err := json.Unmarshal([]byte(s), &m)
if err != nil {
log.Fatal(err)
}
mm := make(map[string]string)
for k, v := range m {
mm[k] = fmt.Sprint(v)
}
fmt.Println(mm)
}
UPDATE
Wrote flatten (maybe works as charm)
package main
import (
"encoding/json"
"fmt"
"log"
"reflect"
)
const s = `
{
"localhost":
{
"tag": "dev_latest",
"vhost": "localhost.com"
},
"development":
{
"tag": "dev_latest",
"vhost": "dev.com"
}
}
`
func flatten(m map[string]interface{}) map[string]string {
mm := make(map[string]string)
for k, v := range m {
switch reflect.TypeOf(v).Kind() {
case reflect.Map:
mv := flatten(v.(map[string]interface{}))
for kk, vv := range mv {
mm[k+"-"+kk] = vv
}
case reflect.Array, reflect.Slice:
for kk, vv := range m {
if reflect.TypeOf(vv).Kind() == reflect.Map {
mv := flatten(vv.(map[string]interface{}))
for kkk, vvv := range mv {
mm[k+"-"+kkk] = vvv
}
} else {
mm[k+"-"+kk] = fmt.Sprint(vv)
}
}
default:
mm[k] = fmt.Sprint(v)
}
}
return mm
}
func main() {
var m map[string]interface{}
err := json.Unmarshal([]byte(s), &m)
if err != nil {
log.Fatal(err)
}
b, _ := json.MarshalIndent(flatten(m), "", " ")
println(string(b))
}
You can't have this automatically, but you can range over the "internal" maps, and combine the outer keys with the inner keys using simple string concatenation (+ operator). Also it's recommended to unmarshal directly into a value of map[string]map[string]string so you don't need to use type assertions. Also no need to use any external libraries for this, the standard encoding/json package is perfectly enough for this.
Example:
var mm map[string]map[string]string
if err := json.Unmarshal([]byte(src), &mm); err != nil {
panic(err)
}
config := map[string]string{}
for mk, m := range mm {
for k, v := range m {
config[mk+"-"+k] = v
}
}
fmt.Println(config)
Output is as expected (try it on the Go Playground):
map[localhost-tag:dev_latest localhost-vhost:localhost.com
development-tag:dev_latest development-vhost:dev.com]
Since in the question you mentioned undefined number of unknown key-values, you may need to deal with JSON document with unknown number of nesting level and having a value other than string. In this case, you need to Unmarshal json to map[string]interface{}, then use recursion to make flat map. Once the json document unmrashaled to map[string]interface{}, use the following function:
func flatMap(src map[string]interface{}, baseKey, sep string, dest map[string]string) {
for key, val := range src {
if len(baseKey) != 0 {
key = baseKey + sep + key
}
switch val := val.(type) {
case map[string]interface{}:
flatMap(val, key, sep, dest)
case string:
dest[key] = val
case fmt.Stringer:
dest[key] = val.String()
default:
//TODO: You may need to handle ARRAY/SLICE
//simply convert to string using `Sprintf`
//NOTE: modify as needed.
dest[key] = fmt.Sprintf("%v", val)
}
}
}
The working solution adapted from mattn answer at https://play.golang.org/p/9SQsbAUFdY
As pointed by mattn, you may have problem when you want to writeback the configuration value. In that case, use the existing library/framework.