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
Related
golang version: 1.18.3
validator: github.com/go-playground/validator/v10
I want to validate an incoming JSON payload after loaded into nested struct data structure. Here's my incoming JSON payload,
{
"irn": 1,
"firstName": "Testing",
"lastName": "Test",
"cardType": "RECIPROCAL",
"number": "2248974514",
"cardExpiry": {
"month": "01",
"year": "2025"
}
}
here's my medicare.go file
package main
import (
"encoding/json"
"github.com/go-playground/validator/v10"
)
type Medicare struct {
IRN uint8
FirstName string
MiddleInitial string
LastName string
CardType string `validate:"required,eq=RESIDENT|eq=RECIPROCAL|eq=INTERIM"`
Number string
CardExpiry *CardExpiry `validate:"required"`
}
type CardExpiry struct {
Day string
Month string `validate:"required"`
Year string `validate:"required"`
}
Here's my test function
func TestUserPayload(t *testing.T) {
var m Medicare
err := json.Unmarshal([]byte(jsonData), &m)
if err != nil {
panic(err)
}
validate := validator.New()
err = validate.Struct(m)
if err != nil {
t.Errorf("error %v", err)
}
}
I want to do the following validation using validator/v10's required_if tag.
Here's the validation logic,
if (m.CardType == "RECIPROCAL" || m.CardType == "INTERIM") &&
m.CardExpiry.Day == "" {
//validation error
}
required_if can be used based on the field values which are in the same struct (in this case CardExpiry)
Day string `validate:"required_if=Month 01"`
My question is,
can it be done based on the values of one of its enclosing struct's field (in this case Medicare struct)?
for ex:
Day string `validate:"required_if=Medicare.CardType RECIPROCAL"`
if can, how?
Here's the go playground code
you could write custom validation,
playground-solution, documentation for custom validation is available here https://pkg.go.dev/github.com/go-playground/validator#CustomTypeFunc.
Slightly old question, but valix (https://github.com/marrow16/valix) can do such things 'out-of-the-box' using conditions. Example...
package main
import (
"fmt"
"net/http"
"strings"
"github.com/marrow16/valix"
)
type Medicare struct {
IRN uint8 `json:"irn"`
FirstName string `json:"firstName"`
MiddleInitial string `json:"middleInitial"`
LastName string `json:"lastName"`
// set order on this property so that it is evaluated before 'CardExpiry' object is checked...
// (and set a condition based on its value)
CardType string `json:"cardType" v8n:"order:-1,required,&StringValidToken{['RESIDENT','RECIPROCAL','INTERIM']},&SetConditionFrom{Global:true}"`
Number string `json:"number"`
CardExpiry *CardExpiry `json:"cardExpiry" v8n:"required,notNull"`
}
type CardExpiry struct {
// this property is required when a condition of `RECIPROCAL` has been set (and unwanted when that condition has not been set)...
Day string `json:"day" v8n:"required:RECIPROCAL,unwanted:!RECIPROCAL"`
Month string `json:"month" v8n:"required"`
Year string `json:"year" v8n:"required"`
}
var medicareValidator = valix.MustCompileValidatorFor(Medicare{}, nil)
func main() {
jsonData := `{
"irn": 1,
"firstName": "Testing",
"lastName": "Test",
"cardType": "RECIPROCAL",
"number": "2248974514",
"cardExpiry": {
"month": "01",
"year": "2025"
}
}`
medicare := &Medicare{}
ok, violations, _ := medicareValidator.ValidateStringInto(jsonData, medicare)
// should fail with 1 violation...
fmt.Printf("First ok?: %v\n", ok)
for i, v := range violations {
fmt.Printf("Violation[%d]: Message:%s, Property:%s, Path:%s\n", i+1, v.Message, v.Property, v.Path)
}
jsonData = `{
"irn": 1,
"firstName": "Testing",
"lastName": "Test",
"cardType": "RECIPROCAL",
"number": "2248974514",
"cardExpiry": {
"day": "01",
"month": "01",
"year": "2025"
}
}`
ok, _, _ = medicareValidator.ValidateStringInto(jsonData, medicare)
// should be ok...
fmt.Printf("Second ok?: %v\n", ok)
jsonData = `{
"irn": 1,
"firstName": "Testing",
"lastName": "Test",
"cardType": "INTERIM",
"number": "2248974514",
"cardExpiry": {
"month": "01",
"year": "2025"
}
}`
ok, _, _ = medicareValidator.ValidateStringInto(jsonData, medicare)
// should be ok...
fmt.Printf("Third ok?: %v\n", ok)
jsonData = `{
"irn": 1,
"firstName": "Testing",
"lastName": "Test",
"cardType": "INTERIM",
"number": "2248974514",
"cardExpiry": {
"day": "01",
"month": "01",
"year": "2025"
}
}`
ok, violations, _ = medicareValidator.ValidateStringInto(jsonData, medicare)
fmt.Printf("Fourth ok?: %v\n", ok)
for i, v := range violations {
fmt.Printf("Violation[%d]: Message:%s, Property:%s, Path:%s\n", i+1, v.Message, v.Property, v.Path)
}
// or validate directly from a http request...
req, _ := http.NewRequest("POST", "", strings.NewReader(jsonData))
ok, violations, _ = medicareValidator.RequestValidateInto(req, medicare)
fmt.Printf("Fourth (as http.Request) ok?: %v\n", ok)
for i, v := range violations {
fmt.Printf("Violation[%d]: Message:%s, Property:%s, Path:%s\n", i+1, v.Message, v.Property, v.Path)
}
}
On go-playground
Disclosure: I am the author of Valix
I need to create dynamic json i.e whose key value varies, below mentioned is the json
[{"email":"xxx#gmail.com","location":{"set":"Redmond"},"fname":{"set":"xxxxx"},"clicked_time":{"set":"zz"},"domain":{"add":"ttt"}},{"email":"zzz#gmail.com","location":{"set":"Greece"},"fname":{"set":"zzzzz"},"clicked_time":{"set":"zzz"},"domain":{"add":"zxxxx"}}]
I tried using below code:
rows := []map[string]string{}
if i > 0 {
row := make(map[string]string)
for j:=0;j<len(record);j++ {
key := header[j]
value := record[j]
row[key] = value
}
rows = append(rows, row)
}
How may I add set to location and add to domain to create a nested structure as map can have only one type string or nested structure?
Perhaps I have missed the point a little here, but I am not seeing why this is so dynamic in a way that can't be handled by a struct and the json unmarshal method.
Please see the following for an example
https://play.golang.org/p/8nrO36HQGhy
package main
import (
"encoding/json"
"fmt"
)
type (
Details struct {
Email string `json:"email"`
Location Entry `json:"location"`
FName Entry `json:"fname"`
ClickedTime Entry `json:"clicked_time"`
Domain Entry `json:"domain"`
}
Entry struct {
Set string `json:"set"`
Add string `json:"add"`
}
)
func main() {
d := []byte(`[{
"email": "xxx#gmail.com",
"location": {
"set": "Redmond"
},
"fname": {
"set": "xxxxx"
},
"clicked_time": {
"set": "zz"
},
"domain": {
"add": "ttt"
}
}, {
"email": "zzz#gmail.com",
"location": {
"set": "Greece"
},
"fname": {
"set": "zzzzz"
},
"clicked_time": {
"set": "zzz"
},
"domain": {
"add": "zxxxx"
}
}]`)
x := []Details{}
_ = json.Unmarshal(d, &x)
fmt.Printf("%+v\n", x)
}
The bson name is used when performing pipe in mgo.
Struct :
type Training struct {
Id bson.ObjectId `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
Description string `json:"description"`
Level *TrainingLevel `json:"level"`
Preworks []bson.ObjectId `json:"preworks"`
PrePostTests []bson.ObjectId `json:"preposttests" bson:"preposttests"`
TrainingEvaluations []bson.ObjectId `json:"training_evaluations" bson:"training_evaluations"`
TrainerEvaluations []bson.ObjectId `json:"trainer_evaluations" bson:"trainer_evaluations"`
AppCompanyId bson.ObjectId `json:"app_company_id" bson:"app_company_id"`
Company *Company `json:"company"`
}
Function :
func (this *TrainingClass) GetAllTraining() (interface{}, error) {
if !this.tokenInfo.IsAllowed(this.c) {
return nil, tlib.NewTError(common.Error_NoAccess, "You don't have the right!")
}
sess, db := GetDB()
defer sess.Close()
pipeline := []bson.M{
{"$match": bson.M{
"app_company_id": this.tokenInfo.AppCompanyId}},
{"$lookup": bson.M{
"from": "trainingbatch",
"localField": "_id",
"foreignField": "training._id",
"as": "trainingbatches"}},
}
resp := []bson.M{}
db.C(common.C_TRAINING).Pipe(pipeline).All(&resp)
return bson.M{"data": resp}, nil
}
Json result :
{
"data": [
{
"_id": "5995a749dbcfbe4e8cc31378",
"app_company_id": "58b24756e65bd121f6b1a923",
"description": "Description First Training",
"name": "First Training",
"trainingbatches": [
{
"_id": "5995a74adbcfbe4e8cc31379",
"app_company_id": "58b24756e65bd121f6b1a923",
"company": {
"_id": "58b24756e65bd121f6b1a923",
"address": "",
"app_company_id": "58b24756e65bd121f6b1a923",
"fullname": "",
"name": "Tandem",
"phone": ""
},
}
]
}
]
}
As you can see field _id is generated instead of id. That's not happen if I use find or findId. Is there any way to keep using json field no matter what's the query?
The way you're reading the result, it has no idea what the JSON field names are. In order for it to use those tags, it must actually deserialize into the struct where the tags have been specified. When you do:
resp := []bson.M{}
db.C(common.C_TRAINING).Pipe(pipeline).All(&resp)
You're explicitly telling mgo to return BSON results. The object you pass in (a slice of bson.M) has no json tags on it. In order to control the serialization to JSON, you must pass a struct with the JSON tags specified to All:
resp := []Training
db.C(common.C_TRAINING).Pipe(pipeline).All(&resp)
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"}
}
I'm trying to parse the first record in an embedded JSON array and create an object based on a subset of those properties. I have this working, but based on this question, I have to think there is a more elegant/less brittle way of doing this. For a bit more background, this is a result set from a call to the musicbrainz JSON web service, and I am treating the first artists record as the artist I am looking for.
The format of the JSON is like this:
{
"created": "2014-10-08T23:55:54.343Z",
"count": 458,
"offset": 0,
"artists": [{
"id": "83b9cbe7-9857-49e2-ab8e-b57b01038103",
"type": "Group",
"score": "100",
"name": "Pearl Jam",
"sort-name": "Pearl Jam",
"country": "US",
"area": {
"id": "489ce91b-6658-3307-9877-795b68554c98",
"name": "United States",
"sort-name": "United States"
},
"begin-area": {
"id": "10adc6b5-63bf-4b4e-993e-ed83b05c22fc",
"name": "Seattle",
"sort-name": "Seattle"
},
"life-span": {
"begin": "1990",
"ended": null
},
"aliases": [],
"tags": []
},
...
}
Here's the code I have so far. I'd like to be able to use my ArtistCollection type to get around some of the interface{} stuff, but I'm stuck as to how. I also don't want to bother with mapping all of the properties of the artist record, I'm only interested in the "name" and "id" values.
package main
import (
"fmt"
"encoding/json"
)
type Artist struct {
Id string
Name string
}
type ArtistCollection struct {
Artists []Artist
}
func main() {
raw := //json formatted byte array
var topLevel interface{}
err := json.Unmarshal(raw, &topLevel)
if err != nil {
fmt.Println("Uh oh")
} else {
m := topLevel.(map[string]interface{})
//this seems really hacky/brittle, there has to be a better way?
result := (m["artists"].([]interface{})[0]).(map[string]interface{})
artist := new(Artist)
artist.Id = result["id"].(string)
artist.Name = result["name"].(string)
fmt.Println(artist)
}
}
Requisite go playground link
Define a type that matches the structure of the JSON and unmarshal to a value of that type. I use an anonymous type below. Use an array of length one to grab the first artist record:
package main
import (
"encoding/json"
"fmt"
)
type Artist struct {
Id string
Name string
}
func main() {
raw := // JSON formatted byte array
var result struct {
Artists artist
}
err := json.Unmarshal(raw, &result)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%#v\n", result.Artists[0])
}
playground