Writing a JSON of different types in Go (int and string) - json

I'm new to Go and Json, so I might miss a lot of points here.
So what I'm basically trying to do is making a program which performs the simple Fizz-Buzz program and make a JSON out of it.
This program takes two integers (a and b), iterate from a to b (i) and outputs:
"Fizz" if the number is a factor of 3
"Buzz" if the number is a factor of 5
"FizzBuzz" if the number is a factor both and,
i if the number isn't a factor of both
Using this simple code snippet:
func fizzbuzz(a int, b int) string{
str := fmt.Sprint("{\"output\":[")
for i := a ; i <= b; i++ {
if i%5 == 0 && i%3 == 0 {str = fmt.Sprint(str, "\"FizzBuzz\"")
}else if i%3 == 0 {str = fmt.Sprint(str, "\"Fizz\"")
}else if i%5 == 0 {str = fmt.Sprint(str, "\"Buzz\"")
}else {str = fmt.Sprint(str, i)}
str = str + ","
}
str = str[:len(str) - 1]
str = str + "]}"
return str
}
I was able to construct the string that can later on be converted to JSON:
{"output":["FizzBuzz",1,2,"Fizz",4,"Buzz","Fizz",7,8,"Fizz","Buzz",11,"Fizz",13,14,"FizzBuzz"]}
This works fine so far. I'm just wondering, are there any other solutions to making a JSON array of mixed type (integer and strings) on Golang? I've tried struct and marshaling, but a struct seems to have fixed structure.

There are two good options that come to mind.
You can use an interface type.
package main
import (
"encoding/json"
"os"
)
type output struct {
Output []interface{} `json:"output"`
}
func main() {
out := output{
Output: []interface{}{"FizzBuzz", 1, 2, "Fizz"},
}
d, _ := json.Marshal(out)
os.Stdout.Write(d)
}
Output:
{"output":["FizzBuzz",1,2,"Fizz"]}
Or you can use a different JSON library, like gojay, which has a different API for serializing JSON.

Related

how to find most frequent integer in a slice of struct with Golang

*** disclaimer : i'm not a professional developer, been tinkering with golang for about 8 month (udemy + youtube) and still got no idea how to simple problem like below..
Here's the summarize of the problem :
I'm trying to find the most frequent "age" from struct that comes from the decoded .json file (containing string "name" and integer "age").
After that i need to print the "name" based on the maximum occurence frequency of "age".
The printed "name" based on the maximum-occurence of "age" needs to be sorted alpabethically
Input (.json) :
[
{"name": "John","age": 15},
{"name": "Peter","age": 12},
{"name": "Roger","age": 12},
{"name": "Anne","age": 44},
{"name": "Marry","age": 15},
{"name": "Nancy","age": 15}
]
Output : ['John', 'Mary', 'Nancy'].
Explaination : Because the most occurring age in the data is 15 (occured 3 times), the output should be an array of strings with the three people's
name, in this case it should be ['John', 'Mary', 'Nancy'].
Exception :
In the case there are multiple "age" with the same maximum-occurence count, i need to split the data and print them into different arrays (i.e when 'Anne' age is 12, the result is: ['John', 'Mary', 'Nancy'], ['Anne','Peter','Roger']
This is what i've tried (in Golang) :
package main
{
import (
"encoding/json"
"fmt"
"os"
"sort"
)
// 1. preparing the empty struct for .json
type Passanger struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
// 2. load the json file
content, err := os.ReadFile("passanger.json")
if err != nil {
fmt.Println(err.Error())
}
// 3. parse json file to slice
var passangers []Passanger
err2 := json.Unmarshal(content, &passangers)
if err2 != nil {
fmt.Println("Error JSON Unmarshalling")
fmt.Println(err2.Error())
}
// 4. find most frequent age numbers (?)
for _, v := range passangers {
// this code only show the Age on every line
fmt.Println(v.Age)
}
// 5. print the name and sort them apabethically (?)
// use sort.slice package
// implement group based by "max-occurence-age"
}
Been stuck since yesterday, i've also tried to implement the solution from many coding challenge question like :
func majorityElement(arr int) int {
sort.Ints(arr)
return arr[len(arr)/2]
}
but i'm still struggling to understand how to handle the "age" value from the Passanger slice as an integer input(arr int) to code above.
others solution i've found online is by iterating trough map[int]int to find the maximum frequency :
func main(){
arr := []int{90, 70, 30, 30, 10, 80, 40, 50, 40, 30}
freq := make(map[int]int)
for _ , num := range arr {
freq[num] = freq[num]+1
}
fmt.Println("Frequency of the Array is : ", freq)
}
but then again, the .json file contain not only integer(age) but also string(name) format, i still don't know how to handle the "name" and "age" separately..
i really need a proper guidance here.
*** here's the source code (main.go) and (.json) file that i mentioned above :
https://github.com/ariejanuarb/golang-json
What to do before implementing a solution
During my first years of college, my teachers would always repeat something to me and my fellow classmates, don't write code first, especially if you are a beginner, follow these steps instead:
Write what you want to happen
Details the problem into small steps
Write all scenarios and cases when they branch out
Write input and output (method/function signature)
Check they fit each other
Let's follow these steps...
Write what you want to happen
You have well defined your problem so i will skip this step.
Let's detail this further
You have a passenger list
You want to group the passengers by their age
You want to look which are the most common/which have the most passengers.
You want to print the name in alphabetical order
Branching out
Scenario one: one group has a bigger size than all others.
Scenario two: two or more groups has the same size and are bigger than the others.
There might more scenario but they are yours to find
input output ??
Well now that we have found out what we must be doing, we are going to check the input and output of each steps to achieve this goal.
the steps:
You have a passenger list
input => none or filename (string)
output => []Passenger
You want to group the passengers by their age
input => []Passenger // passenger list
output => map[int][]int or map[int][]&Passenger // ageGroups
The first type, the one inside the bracket is the age of the whole group.
The second type, is a list that contains either:
the passenger's position within the list
the address of the object/passenger in the memory
it is not important as long as we can retrieve back easily the passenger from the list without iterating it back again.
You want to look which are the most common/which have the most passengers.
input => groups (ageGroups)
so here we have scenario 1 and 2 branching out... which mean that it must either be valid for all scenario or use a condition to branch them out.
output for scenario 1 => most common age (int)
output for scenario 2 => most common ages ([]int)
we can see that the output for scenario 1 can be merged with the output of scenario 2
You want to print the name in alphabetical order of all passengers in an
ageGroup
input => groups ([]Passenger) + ages ([]int) + passenger list ([]Passenger).
output => string or []byte or nothing if you just print it...
to be honest, you can skip this one if you want to.
After checking, time to code
let's create functions that fit our signature first
type Passenger struct {
Name string `json:"name"`
Age int `json:"age"`
}
func GetPassengerList() []Passenger{
// 2. load the json file
content, err := os.ReadFile("passanger.json")
if err != nil {
fmt.Println(err.Error())
}
// 3. parse json file to slice
var passengers []Passenger
err2 := json.Unmarshal(content, &passengers)
if err2 != nil {
fmt.Println("Error JSON Unmarshalling")
fmt.Println(err2.Error())
}
return passengers
}
// 4a. Group by Age
func GroupByAge(passengers []Passenger) map[int][]int {
group := make(map[int][]int, 0)
for index, passenger := range passengers {
ageGroup := group[passenger.Age]
ageGroup = append(ageGroup, index)
group[passenger.Age] = ageGroup
}
return group
}
// 4b. find the most frequent age numbers
func FindMostCommonAge(ageGroups map[int][]int) []int {
mostFrequentAges := make([]int, 0)
biggestGroupSize := 0
// find most frequent age numbers
for age, ageGroup := range ageGroups {
// is most frequent age
if biggestGroupSize < len(ageGroup) {
biggestGroupSize = len(ageGroup)
mostFrequentAges = []int{age}
} else if biggestGroupSize == len(ageGroup) { // is one of the most frequent age
mostFrequentAges = append(mostFrequentAges, age)
}
// is not one of the most frequent age so does nothing
}
return mostFrequentAges
}
func main() {
passengers := loadPassengers()
// I am lazy but if you want you could sort the
// smaller slice before printing to increase performance
sort.Slice(passengers, func(i, j int) bool {
if passengers[i].Age == passengers[j].Age {
return passengers[i].Name < passengers[j].Name
}
return passengers[i].Age < passengers[j].Age
})
// age => []position
// Length of the array count as the number of occurences
ageGrouper := GroupByAge(passengers)
mostFrequentAges := FindMostCommonAge(ageGrouper)
// print the passenger
for _, age := range mostFrequentAges {
fmt.Println("{")
for _, passengerIndex := range ageGrouper[age] {
fmt.Println("\t", passengers[passengerIndex].Name)
}
fmt.Println("}")
}
}
Should be any more complicated than
Sort the source slice by age and name
Break it up into sequences with a common age, and
As you go along, track the most common
Something like this:
https://goplay.tools/snippet/6pCpkTEaDXN
type Person struct {
Age int
Name string
}
func MostCommonAge(persons []Person) (mostCommonAge int, names []string) {
sorted := make([]Person, len(persons))
copy(sorted, persons)
// sort the slice by age and then by name
sort.Slice(sorted, func(x, y int) bool {
left, right := sorted[x], sorted[y]
switch {
case left.Age < right.Age:
return true
case left.Age > right.Age:
return false
default:
return left.Name < right.Name
}
})
updateMostCommonAge := func(seq []Person) (int, []string) {
if len(seq) > len(names) {
buf := make([]string, len(seq))
for i := 0; i < len(seq); i++ {
buf[i] = seq[i].Name
}
mostCommonAge = seq[0].Age
names = buf
}
return mostCommonAge, names
}
for lo, hi := 0, 0; lo < len(sorted); lo = hi {
for hi = lo; hi < len(sorted) && sorted[lo].Age == sorted[hi].Age; {
hi++
}
mostCommonAge, names = updateMostCommonAge(sorted[lo:hi])
}
return mostCommonAge, names
}
Another approach uses more memory, but is a little simpler. Here, we build a map of names by age and then iterate over it to find the key with the longest list of names.
https://goplay.tools/snippet/_zmMys516IM
type Person struct {
Age int
Name string
}
func MostCommonAge(persons []Person) (mostCommonAge int, names []string) {
namesByAge := map[int][]string{}
for _, p := range persons {
value, found := namesByAge[p.Age]
if !found {
value = []string{}
}
namesByAge[p.Age] = append(value, p.Name)
}
for age, nameList := range namesByAge {
if len(nameList) > len(names) {
mostCommonAge, names = age, nameList
}
}
return mostCommonAge, names
}

Abnormal JSON Parse

I have a JSON String as {1} or possibly {2} and I need to parse it and obtain the integer parsed.
I know I'm doing this incorrectly, but here's what I have thus far:
package main
import (
"fmt"
"encoding/json"
)
func main(){
jsonStr:="{1}"
jsonData:=[]byte(jsonStr)
var v uint
json.Unmarshal(jsonData, &v)
data:=v
fmt.Println(data)
}
In this example, the data variable should contain the integer value of 1 or 2 if the jsonStr value is {2}
From my experience with JSON and Go I usually use a struct and pass that into the Unmarshalling function, but i cannot make a struct out of this data response.
I looked at the API Documentation and found no solution for a string parse without a struct
This seemed to work for me:
import "regexp"
re:=regexp.MustCompile("[0-9A-Za-z]+")
val:=re.FindAllString(jsonStr,-1)[0]
Some alternatives to regexes since they are resource heavy and tend to be slower than other solutions, errors are ignored for brevity. In prod they wouldn't be.
package main
import (
"fmt"
"strconv"
)
func main() {
str := "{1}"
num, _ := strconv.ParseInt(string(str[1]), 10, 64)
fmt.Println(num)
}
or something more robust that doesn't care about the number of digits in the {} field.
package main
import (
"fmt"
"strconv"
)
func main() {
str := "{341}"
num, _ := strconv.ParseInt(string(str[1:len(str)-1]), 10, 64)
fmt.Println(num)
}
Small benchmark image showing how much slower regexes are than other solutions. They should be used when other options are available or if performance isn't an issue/concern.
Even something like this will out perform a regex
var n string
for _, r := range str {
if unicode.IsDigit(r) {
n += string(r)
}
}
code for benchmark
https://goplay.space/#PLMtSrMTN9k
As noted by commenters, your example strings are not valid JSON since brace-delimited JSON documents must be objects with a list of key-value pairs (e.g. {"x":1}, and numbers are plain (e.g. 2).
However, this simple notation of a "brace enclosed integer" could easily be parsed a number of ways, ultimately by checking the syntax of OPEN_BRACE, DIGIT+, CLOSE_BRACE.
The example code below checks that the first rune of a given string is an open brace {, the last is a close brace }, and everything in between can be parsed as an integer using strconv.ParseInt(...):
func main() {
ss := []string{"{1}", "{2}", "{-123}", "{foo}", "{10", "20}", "30"}
for _, s := range ss {
x, err := parseBraceNumber(s)
fmt.Printf("s=%-10qx=%-10derr=%v\n", s, x, err)
}
// s="{1}" x=1 err=<nil>
// s="{2}" x=2 err=<nil>
// s="{-123}" x=-123 err=<nil>
// s="{foo}" x=0 err=invalid brace number "{foo}"
// s="{10" x=0 err=invalid brace number "{10"
// s="20}" x=0 err=invalid brace number "20}"
// s="30" x=0 err=invalid brace number "30"
}
func parseBraceNumber(s string) (int64, error) {
if len(s) < 3 || s[0] != '{' || s[len(s)-1] != '}' {
return 0, fmt.Errorf("invalid brace number %q", s)
}
x, err := strconv.ParseInt(s[1:len(s)-1], 10, 64)
if err != nil {
return 0, fmt.Errorf("invalid brace number %q", s)
}
return x, nil
}
Of course, other strategies are possible (e.g. using regular expressions) and ultimately it is up to you to decide what implementation is best for your use case.

Go JSON Naming strategy

I am very new in Go and I was exploring go to use for one of my micro services. I was wonder the way Go converts objects to Json and back to Json. But unfortunately i found configuring the output field names are little difficult with the use of tag names.
type MyStruct strust{
MyName string
}
will converts to json
{
"MyName" : "somestring"
}
But we are following a naming strategy for the whole api across Organization to follow snake_case
{
"my_name" : "somestring"
}
Is considered to be valid in my org.
I started using the tags like json:"my_name,omitempty" etc per field level.
I would like to know is there a way i can configure it at global project level, so that i dont want to take care this at every object and its field level.
You could try something like this: https://play.golang.org/p/Vn-8XH_jLp5
Core functionality:
// SnakeCaseEncode snake_case's the given struct's field names.
func SnakeCaseEncode(i interface{}) map[string]interface{} {
rt, rv := reflect.TypeOf(i), reflect.ValueOf(i)
if rt.Kind() == reflect.Ptr {
i := reflect.Indirect(rv).Interface()
rt, rv = reflect.TypeOf(i), reflect.ValueOf(i)
}
out := make(map[string]interface{}, rt.NumField())
for i := 0; i < rt.NumField(); i++ {
if rt.Field(i).Tag.Get("json") == "-" {
continue
}
if strings.Contains(rt.Field(i).Tag.Get("json"), "omitempty") &&
rv.Field(i).IsZero() { // Go 1.13
continue
}
k := snakeCase(rt.Field(i).Name)
out[k] = rv.Field(i).Interface()
}
return out
}
// snakeCase provides basic ASCII conversion of camelCase field names to snake_case.
func snakeCase(s string) string {
out := make([]rune, 0, utf8.RuneCountInString(s))
for i, r := range s {
if r >= 'A' && r <= 'Z' {
r += 32
if i > 0 {
out = append(out, '_')
}
}
out = append(out, r)
}
return string(out)
}
To support map, slice etc. you'll have to expand on this simple version.

Is there a convenient way to convert a JSON-like map[string]interface{} to struct , and vice versa, with tags in Go? [duplicate]

I want to convert a struct to map in Golang. It would also be nice if I could use the JSON tags as keys in the created map (otherwise defaulting to field name).
Edit Dec 14, 2020
Since structs repo was archived, you can use mapstructure instead.
Edit TL;DR version, Jun 15, 2015
If you want the fast solution for converting a structure to map, see the accepted answer, upvote it and use that package.
Happy coding! :)
Original Post
So far I have this function, I am using the reflect package but I don't understand well how to use the package, please bear with me.
func ConvertToMap(model interface{}) bson.M {
ret := bson.M{}
modelReflect := reflect.ValueOf(model)
if modelReflect.Kind() == reflect.Ptr {
modelReflect = modelReflect.Elem()
}
modelRefType := modelReflect.Type()
fieldsCount := modelReflect.NumField()
var fieldData interface{}
for i := 0; i < fieldsCount; i++ {
field := modelReflect.Field(i)
switch field.Kind() {
case reflect.Struct:
fallthrough
case reflect.Ptr:
fieldData = ConvertToMap(field.Interface())
default:
fieldData = field.Interface()
}
ret[modelRefType.Field(i).Name] = fieldData
}
return ret
}
Also I looked at JSON package source code, because it should contain my needed implementation (or parts of it) but don't understand too much.
I also had need for something like this. I was using an internal package which was converting a struct to a map. I decided to open source it with other struct based high level functions. Have a look:
https://github.com/fatih/structs
It has support for:
Convert struct to a map
Extract the fields of a struct to a []string
Extract the values of a struct to a []values
Check if a struct is initialized or not
Check if a passed interface is a struct or a pointer to struct
You can see some examples here: http://godoc.org/github.com/fatih/structs#pkg-examples
For example converting a struct to a map is a simple:
type Server struct {
Name string
ID int32
Enabled bool
}
s := &Server{
Name: "gopher",
ID: 123456,
Enabled: true,
}
// => {"Name":"gopher", "ID":123456, "Enabled":true}
m := structs.Map(s)
The structs package has support for anonymous (embedded) fields and nested structs. The package provides to filter certain fields via field tags.
From struct to map[string]interface{}
package main
import (
"fmt"
"encoding/json"
)
type MyData struct {
One int
Two string
Three int
}
func main() {
in := &MyData{One: 1, Two: "second"}
var inInterface map[string]interface{}
inrec, _ := json.Marshal(in)
json.Unmarshal(inrec, &inInterface)
// iterate through inrecs
for field, val := range inInterface {
fmt.Println("KV Pair: ", field, val)
}
}
go playground here
Here is a function I've written in the past to convert a struct to a map, using tags as keys
// ToMap converts a struct to a map using the struct's tags.
//
// ToMap uses tags on struct fields to decide which fields to add to the
// returned map.
func ToMap(in interface{}, tag string) (map[string]interface{}, error){
out := make(map[string]interface{})
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("ToMap only accepts structs; got %T", v)
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(tag); tagv != "" {
// set key of map to value in struct field
out[tagv] = v.Field(i).Interface()
}
}
return out, nil
}
Runnable example here.
Note, if you have multiple fields with the same tag value, then you will obviously not be able to store them all within a map. It might be prudent to return an error if that happens.
I like the importable package for the accepted answer, but it does not translate my json aliases. Most of my projects have a helper function/class that I import.
Here is a function that solves my specific problem.
// Converts a struct to a map while maintaining the json alias as keys
func StructToMap(obj interface{}) (newMap map[string]interface{}, err error) {
data, err := json.Marshal(obj) // Convert to a json string
if err != nil {
return
}
err = json.Unmarshal(data, &newMap) // Convert to a map
return
}
And in the main, this is how it would be called...
package main
import (
"fmt"
"encoding/json"
"github.com/fatih/structs"
)
type MyStructObject struct {
Email string `json:"email_address"`
}
func main() {
obj := &MyStructObject{Email: "test#test.com"}
// My solution
fmt.Println(StructToMap(obj)) // prints {"email_address": "test#test.com"}
// The currently accepted solution
fmt.Println(structs.Map(obj)) // prints {"Email": "test#test.com"}
}
package main
import (
"fmt"
"reflect"
)
type bill struct {
N1 int
N2 string
n3 string
}
func main() {
a := bill{4, "dhfthf", "fdgdf"}
v := reflect.ValueOf(a)
values := make(map[string]interface{}, v.NumField())
for i := 0; i < v.NumField(); i++ {
if v.Field(i).CanInterface() {
values[v.Type().Field(i).Name] = v.Field(i).Interface()
} else {
fmt.Printf("sorry you have a unexported field (lower case) value you are trying to sneak past. I will not allow it: %v\n", v.Type().Field(i).Name)
}
}
fmt.Println(values)
passObject(&values)
}
func passObject(v1 *map[string]interface{}) {
fmt.Println("yoyo")
}
I'm a bit late but I needed this kind of feature so I wrote this. Can resolve nested structs. By default, uses field names but can also use custom tags. A side effect is that if you set the tagTitle const to json, you could use the json tags you already have.
package main
import (
"fmt"
"reflect"
)
func StructToMap(val interface{}) map[string]interface{} {
//The name of the tag you will use for fields of struct
const tagTitle = "kelvin"
var data map[string]interface{} = make(map[string]interface{})
varType := reflect.TypeOf(val)
if varType.Kind() != reflect.Struct {
// Provided value is not an interface, do what you will with that here
fmt.Println("Not a struct")
return nil
}
value := reflect.ValueOf(val)
for i := 0; i < varType.NumField(); i++ {
if !value.Field(i).CanInterface() {
//Skip unexported fields
continue
}
tag, ok := varType.Field(i).Tag.Lookup(tagTitle)
var fieldName string
if ok && len(tag) > 0 {
fieldName = tag
} else {
fieldName = varType.Field(i).Name
}
if varType.Field(i).Type.Kind() != reflect.Struct {
data[fieldName] = value.Field(i).Interface()
} else {
data[fieldName] = StructToMap(value.Field(i).Interface())
}
}
return data
}
map := Structpb.AsMap()
// map is the map[string]interface{}

How to get the key value from a json string in Go

I would like to try get the key values from JSON in Go, however I'm unsure how to.
I've been able to use simplejson to read json values, however I've not been able to find out how to get the key values.
Would anyone be able to point me in the right direction and/or help me?
Thank you!
You can get the top-level keys of a JSON structure by doing:
package main
import (
"encoding/json"
"fmt"
)
// your JSON structure as a byte slice
var j = []byte(`{"foo":1,"bar":2,"baz":[3,4]}`)
func main() {
// a map container to decode the JSON structure into
c := make(map[string]json.RawMessage)
// unmarschal JSON
e := json.Unmarshal(j, &c)
// panic on error
if e != nil {
panic(e)
}
// a string slice to hold the keys
k := make([]string, len(c))
// iteration counter
i := 0
// copy c's keys into k
for s, _ := range c {
k[i] = s
i++
}
// output result to STDOUT
fmt.Printf("%#v\n", k)
}
Note that the order of the keys must not correspond to the their order in the JSON structure. Their order in the final slice will even vary between different runs of the exact same code. This is because of how map iteration works.
If you don't feel like writing tens of useless structs, you could use something like https://github.com/tidwall/gjson:
gjson.Get(
`{"object": {"collection": [{"items": ["hello"]}]}}`,
"object.collection.items.0",
) // -> "hello"
Plus some weird-useful querying tricks.
Even though the question is old and solved in different ways, I came across an similiar problem and didn't find an easy solution. I only needed 1 of all the values in a huge json-response.
My approach: Simply using a regex over an given string, in this case the JSON formatted string.
The plain string gets filtered for a given key and returns only the value for the given key via this method
// extracts the value for a key from a JSON-formatted string
// body - the JSON-response as a string. Usually retrieved via the request body
// key - the key for which the value should be extracted
// returns - the value for the given key
func extractValue(body string, key string) string {
keystr := "\"" + key + "\":[^,;\\]}]*"
r, _ := regexp.Compile(keystr)
match := r.FindString(body)
keyValMatch := strings.Split(match, ":")
return strings.ReplaceAll(keyValMatch[1], "\"", "")
}
Regarding the given pattern, I didn't test all case, but it's scanning for a string like this
double quote, the name of the key, double quote, an semicolon and any char sequence except "," ";" "}" "]" (so basically anything that could close a key value pair in the syntax of json)
Example:
jsonResp := "{\"foo\":\"bar\"}"
value := extractValue(jsonResp, "foo")
fmt.Println(value)
would simple return bar
The main advantage I see, that you don't need to care about the structure of the JSON-response, but go just for the value you need by key.
Note: I think it's only possible to grab the value of the first matched key. But you can always modify the method. It just makes use of the regex-technology.
I used the following to grab nested keys from JSON:
import (
"bytes"
"encoding/json"
"errors"
"io"
"sort"
)
func keys(b []byte) ([]string, error) {
dec := json.NewDecoder(bytes.NewBuffer(b))
// store unique keys
kmap := make(map[string]struct{})
// is the next Token a key?
var key bool
// keep track of both object and array parents with a slice of bools:
// - an object parent is true, an array parent is false
parents := make([]bool, 0, 10)
for {
t, err := dec.Token()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
del, ok := t.(json.Delim)
if ok {
if del == '{' {
// push an object parent
parents = append(parents, true)
}
if del == '[' {
// push an array parent
parents = append(parents, false)
}
if del == '}' || del == ']' {
if len(parents) == 0 {
return nil, errors.New("bad json: unexpected } or ] delim")
}
// pop the last parent
parents = parents[:len(parents)-1]
}
if len(parents) > 0 && parents[len(parents)-1] {
// if we are within an object, the next token must be a key
key = true
} else {
// otherwise we are in an array, and the next token is an array entry
key = false
}
continue
}
if key {
str, ok := t.(string)
if !ok {
return nil, errors.New("bad json: keys must be strings")
}
kmap[str] = struct{}{}
// if this is a key, then the next token is the value
key = false
} else if len(parents) > 0 && parents[len(parents)-1] {
// if this is a value, and we are within an object, then the next token is a new key
key = true
}
}
// now turn our map of keys into a sorted slice
ret := make([]string, len(kmap))
var i int
for k := range kmap {
ret[i] = k
i++
}
sort.Strings(ret)
return ret, nil
}