How to convert JSON into associative/indexed array - json

Given this JSON
{
"users": [
{
"name" : "Elliot",
"type" : "Reader",
"age" : 23,
"social" : {
"facebook" : "https://facebook.com",
"twitter" : "https://twitter.com"
}
},
{
"name" : "Fraser",
"type" : "Author",
"age" : 17,
"social" : {
"facebook" : "https://facebook.com",
"twitter" : "https://twitter.com"
}
}
]
}
I need a function/library to return a map in order to do myMap[0].name to get the value "Elliot".
Is there anyone who can help me? Thanks.

I wish to access to that field in much beautiful notation associative-array like Res["users"][0].Name, or something like that.
As mentioned in comments, arguably the nicest way would be using structs
type User struct {
Name string `json:"name"`
Type string `json:"type"`
Age int `json:"age"`
Social struct {
Facebook string `json:"facebook"`
Twitter string `json:"twitter"`
} `json:"social"`
}
type Config struct {
Users []User `json:"users"`
}
rawJSON := []byte(`{...your config JSON in here...}`)
config := Config{}
if err := json.Unmarshal(rawJSON, &config); err != nil {
log.Fatal(err)
}
user := config.Users[0]
fmt.Printf(
"name: %s, type: %s, age: %d, facebook: %s, twitter: %s\n",
user.Name,
user.Type,
user.Age,
user.Social.Facebook,
user.Social.Twitter,
)
Result:
name: Elliot, type: Reader, age: 23, facebook: https://facebook.com, twitter: https://twitter.com
Go Playground

Related

How to decode mongodb data to struct

Go Model:
package models
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// News : News Model
type News struct {
ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
Host string `json:"host,omitempty" bson:"host,omitempty"`
Category string `json:"category,omitempty" bson:"category,omitempty"`
Headline string `json:"headline,omitempty" bson:"headline,omitempty"`
Image string `json:"image,omitempty" bson:"image,omitempty"`
URL string `json:"url,omitempty" bson:"url,omitempty"`
Date string `json:"date,omitempty" bson:"date,omitempty"`
ClickCount int64 `json:"clickCount,omitempty" bson:"clickCount,omitempty"`
Archived bool `json:"archived,omitempty" bson:"archived,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"`
}
MongoDB Data that I have:
{
"_id" : ObjectId("5e1d58f6fad87c735bbca592"),
"createdAt" : ISODate("2020-01-14T11:30:22.481Z"),
"clickCount" : 0,
"archived" : false,
"host" : "timesofindia",
"category" : "sports",
"headline" : "Caroline Wozniacki pulls out of Kooyong Classic",
"url" : "https://timesofindia.indiatimes.com/sports/tennis/top-stories/caroline-wozniacki-pulls-out-of-kooyong-classic/articleshow/73238147.cms",
"image" : "https://timesofindia.indiatimes.com/thumb/msid-73238147,width-400,resizemode-4/73238147.jpg",
"date" : "14 Jan 2020, 0958 hrs IST"
}
My API endpoint code:
func AllNews(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "application/json")
collection := config.Client.Database("newspaper").Collection("news")
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
var allNews []models.News
var finalResponse models.FinalResponse
cursor, e := collection.Find(ctx, bson.M{})
if e != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{ "message": "` + e.Error() + `" }`))
return
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var news models.News
cursor.Decode(&news)
allNews = append(allNews, news)
}
finalResponse.Status = "success"
finalResponse.Body = allNews
json.NewEncoder(w).Encode(finalResponse)
}
Challenge that I am facing right now is in output, I can not see "clickCount" and "archived".
Output:
{
"status": "success",
"body": [
{
"_id": "5e1d58f6fad87c735bbca588",
"host": "timesofindia",
"category": "business",
"headline": "Bandhan Bank all set to announce its Q3 results today",
"image": "https://timesofindia.indiatimes.com/thumb/msid-73239715,width-400,resizemode-4/73239715.jpg",
"url": "https://timesofindia.indiatimes.com/business/india-business/bandhan-bank-all-set-to-announce-its-q3-results-today/articleshow/73239715.cms",
"date": "14 Jan 2020, 1111 hrs IST",
"createdAt": "2020-01-14T11:30:22.442Z"
}
]
}
I tried changing data types to int32 and string on both fields it still didn't work.
If I change the data type of these two field then in "body" of output I only see "id" and "createdAt"
Let me know if more data is needed.
You should remove the clickCount and archived JSON tag omitempty

Nested Go Structs for JSON marshaling with optional structs

I'm trying to initialize a nested struct to then marshal into json for an API response. The challenge I'm hitting is one of the components (a slice of structs) can have n number of members but of one of two possible types (text, image).
The JSON I want to create looks like this:
{
"messages": [
{
"message_parts": [
{
"text": {
"content": "dfdffd"
}
},
{
"image": {
"url": "https://image.jpg"
}
}
],
"actor_id": "44444444",
"actor_type": "agent"
}
],
"channel_id": "44444444",
"users": [
{
"id": "44444444"
}
]
}
In the message_parts slice, that can contain at least one of text or image but possibly one of each.
My structs look like this currently:
Type messagePayload struct {
Messages []Messages `json:"messages"`
Status string `json:"status,omitempty"`
ChannelID string `json:"channel_id"`
Users []Users `json:"users"`
}
type Messages struct {
MessageParts []MessageParts `json:"message_parts"`
ActorID string `json:"actor_id"`
ActorType string `json:"actor_type"`
}
type Users struct {
ID string `json:"id"`
}
type Text struct {
Content string `json:"content,omitempty"`
}
type MessageParts struct {
Text *Text `json:"text,omitempty"`
Image *Image `json:"image,omitempty"`
}
type Image struct {
URL string `json:"url,omitempty"`
}
I'm really struggling to initialize this in a way that not show up in the json if they're not present.
here's where I'm at but it obviously doesn't work:
payload := &messagePayload{
Messages: []Messages{
{
MessageParts: []MessageParts{
{
&Text{
Content: text,
},
},
{
&Image{
URL: mediaurl,
},
},
},
ActorID: agentID,
ActorType: "agent",
}},
ChannelID: channelid,
Users: []Users{
{
ID: user,
},
},
}
EDIT:
Thanks to hint below and a few other findings, I've found the best way is to initalize the payload and then add the slices for text and images as needed:
https://play.golang.org/p/Pmmv00spcI6
As noted above, I was able to find a solution- you need to initialize the payload without the text or image data, then append them to the MessageParts slice:
package main
import (
"encoding/json"
"fmt"
)
type messagePayload struct {
Messages []Messages `json:"messages"`
Status string `json:"status,omitempty"`
ChannelID string `json:"channel_id"`
Users []Users `json:"users"`
}
type Messages struct {
MessageParts []MessageParts `json:"message_parts"`
ActorID string `json:"actor_id"`
ActorType string `json:"actor_type"`
}
type Users struct {
ID string `json:"id"`
}
type Text struct {
Content string `json:"content,omitempty"`
}
type MessageParts struct {
Text *Text `json:"text,omitempty"`
Image *Image `json:"image,omitempty"`
}
type Image struct {
URL string `json:"url,omitempty"`
}
func main() {
payload := &messagePayload{
Messages: []Messages{
{
MessageParts: []MessageParts{
},
ActorID: "id",
ActorType: "agent",
}},
ChannelID: "cid",
Users: []Users{
{
ID: "user1",
},
},
}
var text= new(MessageParts)
text.Text = &Text{Content: "LOL"}
var image = new(MessageParts)
image.Image= &Image{URL: "https://"}
payload.Messages[0].MessageParts = append(payload.Messages[0].MessageParts, *text)
payload.Messages[0].MessageParts = append(payload.Messages[0].MessageParts, *image)
m, err := json.Marshal(payload)
if err != nil {
fmt.Println("Error, ", err)
return
}
fmt.Println(string(m))
}

Control field visibility depending on User role

I'd like to hide/show some fields of a model depending on User role.
What would be the most idiomatic way to implement it?
I don't really want to create N different types of the same model (where N is amount of User roles). Like:
UserEmployee, AdminEmployee, WhateverEmployee.
It would be perfect if there is some solution that uses the tags for it:
type Employee struct {
ID string `visibility:"admin,user"`
Name string `visibility:"admin,user"`
Salary int `visibility:"admin"`
}
jsonBytes, _ := someLib.Marshal(Employee{"1", "John", 5000}, "user")
fmt.Println(string(jsonBytes)) // {"id":"1","name":"John"}
The question is really pretty broad. I just wanted to know how you handle this situation or what is the most common way to do it in the Go community. I want clean and centralized (same for all models) solution that won't require to produce tons of duplicated code.
What have I tried before: I've just tried to use separate models for all cases and cast between them.
Create an empty struct of your type (Employee in this problem) that will hold the filtered data.
Use the reflect package to compare if the field tag contains the desired tag value (visibility role).
Copy values of base struct to our filter struct when we find a tag match and json marshal the output struct:
package main
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
type Employee struct {
ID string `visibility:"admin, hr, user" json:"id,omitempty"`
Name string `visibility:"admin, hr, user" json:"name,omitempty"`
Salary int `visibility:"admin, hr" json:"salary,omitempty"`
Password string `visibility:"admin" json:"password,omitempty"`
Rights map[string]bool `visibility:"admin" json:"rights,omitempty"`
Boss *Employee `visibility:"admin, hr" json:"boss,omitempty"`
}
func filterEmployee(emp Employee, role string) Employee {
var fEmployee Employee
ev := reflect.ValueOf(emp)
et := reflect.TypeOf(emp)
// Iterate through each field within the struct
for i := 0; i < ev.NumField(); i++ {
v := ev.Field(i)
t := et.Field(i)
roles := t.Tag.Get("visibility")
if strings.Contains(roles, role) {
switch i {
case 0: // ID
fEmployee.ID = v.String()
case 1: // Name
fEmployee.Name = v.String()
case 2: // Salary
fEmployee.Salary = int(v.Int())
case 3: // Password
fEmployee.Password = v.String()
case 4: // Rights
fEmployee.Rights = v.Interface().(map[string]bool)
case 5: // Boss
fEmployee.Boss = v.Interface().(*Employee)
}
}
}
return fEmployee
}
func main() {
e := Employee{
"1",
"Jack",
100000,
"password321",
map[string]bool{"create": false, "update": false},
&Employee{
"2",
"John",
120000,
"pwd",
map[string]bool{"create": true, "update": true},
nil,
},
}
fuser := filterEmployee(e, "user")
fhr := filterEmployee(e, "hr")
fadmin := filterEmployee(e, "admin")
buser, err := json.MarshalIndent(fuser, "", " ")
if err != nil {
fmt.Println(err)
}
fmt.Println("Filtering with role user: ")
fmt.Println(string(buser))
bhr, err := json.MarshalIndent(fhr, "", " ")
if err != nil {
fmt.Println(err)
}
fmt.Println("\nFiltering with role hr: ")
fmt.Println(string(bhr))
badmin, err := json.MarshalIndent(fadmin, "", " ")
if err != nil {
fmt.Println(err)
}
fmt.Println("\nFiltering with role admin: ")
fmt.Println(string(badmin))
}
Output:
Filtering with role user:
{
"id": "1",
"name": "Jack"
}
Filtering with role hr:
{
"id": "1",
"name": "Jack",
"salary": 100000,
"boss": {
"id": "2",
"name": "John",
"salary": 120000,
"password": "pwd",
"rights": {
"create": true,
"update": true
}
}
}
Filtering with role admin:
{
"id": "1",
"name": "Jack",
"salary": 100000,
"password": "password321",
"rights": {
"create": false,
"update": false
},
"boss": {
"id": "2",
"name": "John",
"salary": 120000,
"password": "pwd",
"rights": {
"create": true,
"update": true
}
}
}
Playground
EDIT: Updated answer for asker's request.
View the old playground for previous answer that ran into issues.
Old Playground
Use "omit empty"
type Employee struct {
ID string `json:",omitempty"`
Name string `json:",omitempty"`
Salary int `json:",omitempty"`
}
Your function can look like
func MarshallEmployee(e Employee, permission string) {
if permission == "user"{
e.Salary = 0
}
....marshall it
}
or you could also just not add the value to the struct in the first place. See the docs for more detail.
use this module: https://github.com/icoom-lab/marian/
package main
import (
"fmt"
"github.com/icoom-lab/marian"
)
type Account struct {
Id int `json:"id,omitempty" role:"admin"`
Name string `json:"name,omitempty" role:"admin,normal"`
}
func main() {
account := Account{
Id: 1,
Name: "Jhon",
}
fmt.Println(account)
// {id:1, Name:"Jhon"}
marian.CleanStruct("role", "admin", &account)
fmt.Println(account)
// {id:1, Name:"Jhon"}
marian.CleanStruct("role", "normal", &account)
fmt.Println(account)
// {Name:"Jhon"}
}

Encoding nested JSON in Go

I've had a lot of trouble finding an example of this. Most of the information on the internet is about decoding JSON.
I'd like to serialize some data into nested JSON, like for example:
{
"item": {
"title": "Items",
"properties": [
{
"num": 1,
"name": "Item 1"
},
{
"num": 2,
"name": "Item 2"
}
]
}
}
I know how to marshal data with a flat struct, but how do I put data into a struct that can be serialized with nesting?
http://play.golang.org/p/nDKmv1myTD
I found this tool that generates a struct from a JSON schema, but I don't understand how to get data into the sub structs.
http://mholt.github.io/json-to-go/
type Example struct {
Item struct {
Title string `json:"title"`
Properties []struct {
Num int `json:"num"`
Name string `json:"name"`
} `json:"properties"`
} `json:"item"`
}
This tool you found is nice, but I would not use it. It makes it difficult to initialize the structs.
Init example with your snippet: (http://play.golang.org/p/_Qw3Qp8XZh)
package main
import (
"encoding/json"
"fmt"
)
type Example struct {
Item struct {
Title string `json:"title"`
Properties []struct {
Num int `json:"num"`
Name string `json:"name"`
} `json:"properties"`
} `json:"item"`
}
func main() {
info := &Example{
Item: struct {
Title string `json:"title"`
Properties []struct {
Num int `json:"num"`
Name string `json:"name"`
} `json:"properties"`
}{
Title: "title",
Properties: []struct {
Num int `json:"num"`
Name string `json:"name"`
}{
{Num: 0, Name: "name0"},
{Num: 1, Name: "name1"},
},
},
}
b, err := json.Marshal(info)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
Result (pretty printed):
{
"item": {
"title": "title",
"properties": [
{
"num": 0,
"name": "name0"
},
{
"num": 1,
"name": "name1"
}
]
}
}
I think it is better to use named struct vs anonymous nested ones.
Same example with named structs: http://play.golang.org/p/xm7BXxEGTC
package main
import (
"encoding/json"
"fmt"
)
type Example struct {
Item Item `json:"item"`
}
type Item struct {
Title string `json:"title"`
Properties []Property `json:"properties"`
}
type Property struct {
Num int `json:"num"`
Name string `json:"name"`
}
func main() {
info := &Example{
Item: Item{
Title: "title",
Properties: []Property{
{Num: 0, Name: "name0"},
{Num: 1, Name: "name1"},
},
},
}
b, err := json.Marshal(info)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
}
It is the exact same thing, but I find it more clear and easy to use.

Golang - How do you decode json array and get the root property

I can't figure out how to decode this JSON in Go. The map returns nil. Unmarshal works from memory, but eventually I might need a stream. Also, I need to get Foo, Bar and Baz key names. Not sure about that one.
JSON:
{
"Foo" : {"Message" : "Hello World 1", "Count" : 1},
"Bar" : {"Message" : "Hello World 2", "Count" : 0},
"Baz" : {"Message" : "Hello World 3", "Count" : 1}
}
Code:
package main
import (
"encoding/json"
"fmt"
"os"
)
type Collection struct {
FooBar map[string]Data
}
type Data struct {
Message string `json:"Message"`
Count int `json:"Count"`
}
func main() {
//will be http
file, err := os.Open("stream.json")
if err != nil {
panic(err)
}
decoder := json.NewDecoder(file)
var c Collection
err = decoder.Decode(&c)
if err != nil {
panic(err)
}
for key, value := range c.FooBar {
fmt.Println("Key:", key, "Value:", value)
}
//returns empty map
fmt.Println(c.FooBar)
}
You don't need a top-level struct, decode directly into a map:
err = decoder.Decode(&c.FooBar)
Or, just remove the struct:
type Collection map[string]Data
With your top-level struct, the implied format is:
{
"FooBar": {
"Foo" : {"Message" : "Hello World 1", "Count" : 1},
"Bar" : {"Message" : "Hello World 2", "Count" : 0},
"Baz" : {"Message" : "Hello World 3", "Count" : 1}
}
}