How to create Category Tree in GoLang - go

I have a struct like that.
type Category struct {
ID int `json:"id"`
MetaDescription string `json:"metaDescription" gorm:"column:metadescription"`
MetaTitle string `json:"metaTitle" gorm:"column:metatitle"`
Name string `json:"name" gorm:"column:categoryname"`
Order int `json:"order" gorm:"column:categoryorder"`
ParentId int `json:"parentId" gorm:"column:parentid"`
Rank float64 `json:"rank" gorm:"column:rank"`
Url string `json:"url" gorm:"column:catname4seo"`
}
I'm fetching this data from a database and I want to manipulate it to create a category tree. My tree would be like that
type CategoryTree struct {
ID int `json:"id"`
Name string `json:"name" gorm:"column:categoryname"`
Url string `json:"url" gorm:"column:catname4seo"`
SubCategories []CategoryTree `json:"subCategories"`
}
Normally, I am a JavaScript dev and a newbie in GoLang so especially static type made me harder. That's why I couldn't achieve it. Can you help me to do so?
Thanks in advance.

I think this is an algorithm question. Just iterating over the Category list, and push elements into CategoryTree. Go code like this:
package main
import (
"encoding/json"
"fmt"
)
type Category struct {
ID int `json:"id"`
MetaDescription string `json:"metaDescription" gorm:"column:metadescription"`
MetaTitle string `json:"metaTitle" gorm:"column:metatitle"`
Name string `json:"name" gorm:"column:categoryname"`
Order int `json:"order" gorm:"column:categoryorder"`
ParentId int `json:"parentId" gorm:"column:parentid"`
Rank float64 `json:"rank" gorm:"column:rank"`
Url string `json:"url" gorm:"column:catname4seo"`
}
// load db data by CategoryTree instead of Category
type CategoryTree struct {
Category
SubCategories []*CategoryTree `json:"subCategories"`
}
func main() {
input := []*CategoryTree{
{
Category: Category{
ID: 1,
ParentId: 0,
},
},
{
Category: Category{
ID: 2,
ParentId: 1,
},
},
{
Category: Category{
ID: 3,
ParentId: 1,
},
},
{
Category: Category{
ID: 4,
ParentId: 2,
},
},
}
tree := makeTree(input)
output, _ := json.MarshalIndent(tree, "", " ")
fmt.Printf("%s\n", string(output))
}
func makeTree(list []*CategoryTree) *CategoryTree {
dataMap := make(map[int]*CategoryTree, len(list))
var rootNode *CategoryTree
for i := range list {
// root node has min ParentID
if rootNode == nil || rootNode.ParentId > list[i].ParentId {
rootNode = list[i]
}
// list to map
dataMap[list[i].ID] = list[i]
}
// build tree
for i := range list {
parentNode, ok := dataMap[list[i].ParentId]
if !ok {
continue
}
if parentNode.SubCategories == nil {
parentNode.SubCategories = []*CategoryTree{}
}
parentNode.SubCategories = append(parentNode.SubCategories, list[i])
}
return rootNode
}
and output:
{
"id": 1,
"metaDescription": "",
"metaTitle": "",
"name": "",
"order": 0,
"parentId": 0,
"rank": 0,
"url": "",
"subCategories": [
{
"id": 2,
"metaDescription": "",
"metaTitle": "",
"name": "",
"order": 0,
"parentId": 1,
"rank": 0,
"url": "",
"subCategories": [
{
"id": 4,
"metaDescription": "",
"metaTitle": "",
"name": "",
"order": 0,
"parentId": 2,
"rank": 0,
"url": "",
"subCategories": null
}
]
},
{
"id": 3,
"metaDescription": "",
"metaTitle": "",
"name": "",
"order": 0,
"parentId": 1,
"rank": 0,
"url": "",
"subCategories": null
}
]
}

Related

GORM .Save don't save "has one" relation to the database

I have struct:
type Book struct {
gorm.Model
Title string `json:"title"`
Author string `json:"author"`
Description string `json:"description"`
Category string `json:"Category"`
Publisher string `json:"publisher"`
AuthorsCard AuthorsCard `gorm:"foreignKey:BookID" json:"authorscard"`
}
type AuthorsCard struct {
//gorm.Model // I purposely Don't want to use gorm.model
ID uint `gorm:"primarykey"`
BookID uint
Name string `json:"name"`
Age int `json:"age"`
YearOfBirth int `json:"year"`
Biography string `json:"biography"`
}
And I'm trying to make update function:
// controller.go
func UpdateBook(ctx *gin.Context) {
enableCors(&ctx.Writer)
id := ctx.Param("ID")
var updateBook = &models.Book{}
if err := ctx.BindJSON(updateBook); err != nil {
ctx.AbortWithStatus(http.StatusBadRequest)
log.Fatal(err)
} else {
repository.UpdateBook(updateBook, id)
ctx.JSON(http.StatusOK, updateBook)
log.Println(updateBook)
}
}
//repository.go
func UpdateBook(updateBook *models.Book, ID string) {
book, db := GetBookById(ID)
if updateBook.Title != "" {
book.Title = updateBook.Title
}
if updateBook.Author != "" {
book.Author = updateBook.Author
}
if updateBook.Publisher != "" {
book.Publisher = updateBook.Publisher
}
if updateBook.Description != "" {
book.Description = updateBook.Description
}
if updateBook.Category != "" {
book.Category = updateBook.Category
}
if updateBook.AuthorsCard.Name != "" {
book.AuthorsCard.Name = updateBook.AuthorsCard.Name
}
if updateBook.AuthorsCard.Age != 0 {
book.AuthorsCard.Age = updateBook.AuthorsCard.Age
}
if updateBook.AuthorsCard.YearOfBirth != 0 {
book.AuthorsCard.YearOfBirth = updateBook.AuthorsCard.YearOfBirth
}
if updateBook.AuthorsCard.Biography != "" {
book.AuthorsCard.Biography = updateBook.AuthorsCard.Biography
}
db.Save(&book)
// same with db.Preload("AuthorsCard").Save(&book)
}
The issue is: When I make an PUT request, I receive Fully updated data. And when I'm trying to make GET request all my fields, except related AuthorsCard, are been updated.
PUT response:
200 Code
{
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"title": "Test",
"author": "author",
"description": "something",
"Category": "Category",
"publisher": "PB",
"authorscard": {
"ID": 0,
"BookID": 0,
"name": "Updated",
"age": 22,
"year": 1999,
"biography": "biography Updated"
}
}
Get response after that:
[
{
"ID": 1,
"CreatedAt": "2022-06-29T14:57:37.489639+03:00",
"UpdatedAt": "2022-06-29T15:50:11.578724+03:00",
"DeletedAt": null,
"title": "Test",
"author": "author",
"description": "something",
"Category": "Category",
"publisher": "PB",
"authorscard": {
"ID": 1,
"BookID": 1,
"name": "test",
"age": 23,
"year": 1999,
"biography": "23fdgsdddTEST"
}
}
]
As you can see "authorscard" hasn't changed. Please, can someone tell me what I'm doing wrong?
You need to explicitly tell Gorm to store/update associations.
db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&book)

gORM belongs to association doesnt preload

I am trying to make a relational database with gORM and I have a problem with the belongs to method that I can't get to work.
I am expecting to see the booking with the table it is related to but the booking just shows a table with nil values. and the TableID that it should refer to is also null.
I have created a small test which creates some data and prints out the results as json.
package main
import (
"encoding/json"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Username string
Password string
Bookings []Booking `gorm:"polymorphic:Owner;"`
}
type Booking struct {
gorm.Model
TableID *int
Table Table
OwnerID uint
Time int
OwnerType string
}
type Table struct {
gorm.Model
Number int
Price int
}
func DatabaseConnection() *gorm.DB {
db, err := gorm.Open(sqlite.Open("./test.db"), &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&Booking{})
db.AutoMigrate(&Table{})
db.AutoMigrate(&User{})
return db
}
func main() {
fmt.Println("creating stuff..")
createStuff()
fmt.Println("checking table..")
checkTable()
fmt.Println("checking booking..")
checkBooking()
fmt.Println("checking user..")
checkUser()
}
func checkTable() {
db := DatabaseConnection()
var table Table
db.First(&table)
u, _ := json.Marshal(&table)
fmt.Println(string(u))
}
func checkUser() {
db := DatabaseConnection()
var user User
db.First(&user)
db.Preload("Bookings.Table").Preload("Bookings").First(&user)
u, _ := json.Marshal(&user)
fmt.Println(string(u))
}
func checkBooking() {
db := DatabaseConnection()
var booking Booking
db.Preload("Table").First(&booking)
u, _ := json.Marshal(&booking)
fmt.Println(string(u))
}
func createStuff() {
db := DatabaseConnection()
tables := []Table{
{Number: 1, Price: 100},
{Number: 2, Price: 100},
{Number: 3, Price: 100},
{Number: 4, Price: 100},
{Number: 5, Price: 100},
{Number: 6, Price: 100},
{Number: 7, Price: 100},
}
user := User{
Username: "martin",
Password: "secret",
}
db.Create(&user)
db.Create(&tables)
booking := Booking{Time: 1234}
db.Model(&tables[0]).Association("Table").Append(
&booking,
)
db.Model(&user).Association("Bookings").Append(
&booking,
)
}
I get the following output from my test:
checking table..
{
"ID": 1,
"CreatedAt": "2021-12-06T14:56:27.169847346+01:00",
"UpdatedAt": "2021-12-06T14:56:27.169847346+01:00",
"DeletedAt": null,
"Number": 1,
"Price": 100
}
checking booking..
{
"ID": 1,
"CreatedAt": "2021-12-06T14:56:27.173323491+01:00",
"UpdatedAt": "2021-12-06T14:56:27.173323491+01:00",
"DeletedAt": null,
"TableID": null,
"Table": {
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Number": 0,
"Price": 0
},
"OwnerID": 1,
"Time": 1234,
"OwnerType": "users"
}
checking user..
{
"ID": 1,
"CreatedAt": "2021-12-06T14:56:27.166274122+01:00",
"UpdatedAt": "2021-12-06T14:56:27.17309057+01:00",
"DeletedAt": null,
"Username": "martin",
"Password": "secret",
"Bookings": [
{
"ID": 1,
"CreatedAt": "2021-12-06T14:56:27.173323491+01:00",
"UpdatedAt": "2021-12-06T14:56:27.173323491+01:00",
"DeletedAt": null,
"TableID": null,
"Table": {
"ID": 0,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"DeletedAt": null,
"Number": 0,
"Price": 0
},
"OwnerID": 1,
"Time": 1234,
"OwnerType": "users"
}
]
}

array of struct object not getting return in response [duplicate]

This question already has answers here:
json.Marshal(struct) returns "{}"
(3 answers)
Closed 1 year ago.
My model having following data:
package main
type Subject struct {
name string `json:name`
section int `json:section`
}
var subjects = map[string][]Subject{
"1001": []Subject{
{
name: "Phy",
section: 1,
},
{
name: "Phy",
section: 2,
},
},
"1002": []Subject{
{
name: "Chem",
section: 1,
},
{
name: "Chem",
section: 2,
},
},
"1003": []Subject{
{
name: "Math",
section: 1,
},
{
name: "Math",
section: 2,
},
},
"1004": []Subject{
{
name: "Bio",
section: 1,
},
{
name: "Bio",
section: 2,
},
},
}
I am creating route as follows:
route.GET("/subjects/:id", func(c *gin.Context) {
id := c.Param("id")
subjects := subjects[id]
c.JSON(http.StatusOK, gin.H{
"StudentID": id,
"Subject": subjects,
})
})
It tried to call it using postman as : localhost:8080/subjects/1001
but it just shows {} {} instead of array of subject struct's objects.
Output:
{
"StudentID": "1001",
"Subject": [
{},
{}
]
}
This is because your Subject uses lowercase fields name and section and thus will not be serialized.
Changing it to:
type Subject struct {
Name string `json:"name"`
Section int `json:"section"`
}
Will show the fields:
{
"StudentID": "1001",
"Subject": [
{"name":"Phy","section":1},
{"name":"Phy","section":2}
]
}

Unmarshalling a complicated JSON ad bin it with a struct

I have the following JSOn response from a webhook call
{
"responseId": "d5c70d8b-e8ad-41df-bb3b-26b0e51d60ca-a14fa99c",
"queryResult": {
"queryText": "1111111111",
"parameters": {
"phone-number": "1111111111"
},
"allRequiredParamsPresent": true,
"fulfillmentText": "Thats great! You payment link has been sent to Gaf ( Mobile number 1111111111 )",
"fulfillmentMessages": [{
"text": {
"text": ["Thats great! You payment link has been sent to Far ( Mobile number 1111111111 )"]
}
}],
"outputContexts": [{
"name": "projects/open-prod-bot-pfgibi/agent/sessions/80f2fb70-01d0-fc1d-200a-ccbae5572829/contexts/awaiting_name",
"lifespanCount": 2,
"parameters": {
"name": ["Gar"],
"name.original": ["Gar"],
"phone-number": "1111111111",
"phone-number.original": "1111111111"
}
}, {
"name": "projects/open-prod-bot-pfgibi/agent/sessions/80f2fb70-01d0-fc1d-200a-ccbae5572829/contexts/awaiting_number",
"lifespanCount": 4,
"parameters": {
"phone-number": "1111111111",
"phone-number.original": "1111111111"
}
}, {
"name": "projects/open-prod-bot-pfgibi/agent/sessions/80f2fb70-01d0-fc1d-200a-ccbae5572829/contexts/awaiting_name_confirm",
"lifespanCount": 3,
"parameters": {
"name": ["Gaf"],
"name.original": ["Far"],
"phone-number": "1111111111",
"phone-number.original": "1111111111"
}
}, {
"name": "projects/open-prod-bot-pfgibi/agent/sessions/80f2fb70-01d0-fc1d-200a-ccbae5572829/contexts/__system_counters__",
"parameters": {
"no-input": 0.0,
"no-match": 0.0,
"phone-number": "1111111111",
"phone-number.original": "1111111111"
}
}],
"intent": {
"name": "projects/open-prod-bot-pfgibi/agent/intents/d21f7be5-0f77-4cb8-9857-26ba04964317",
"displayName": "GetMobileNumber"
},
"intentDetectionConfidence": 1.0,
"languageCode": "en"
},
"originalDetectIntentRequest": {
"payload": {
}
},
"session": "projects/open-prod-bot-pfgibi/agent/sessions/80f2fb70-01d0-fc1d-200a-ccbae5572829"
}
I wanted to extract out
"outputContexts": [{
"name": "projects/open-prod-bot-pfgibi/agent/sessions/80f2fb70-01d0-fc1d-200a-ccbae5572829/contexts/awaiting_name",
"lifespanCount": 2,
"parameters": {
"name": ["Gar"],
"name.original": ["Gar"],
"phone-number": "1111111111",
"phone-number.original": "1111111111"
}
}
out of this and bind this with a struct. But I couldn't do it. I am trying to loop it through the map as below
var f interface{}
json.Unmarshal(b, &f)
for k, v := range f.(map[string]interface{}) {
if k == "queryResultmap" {
fmt.Println(v)
}
}
but not working. I am new to Go. Tried few examples in google but since this one is a complicated JSON I am unable to do it. Please help
I would suggest you declare a struct with the fields you care about and unmarshal into that, but if you want to stick to interface{} try this:
m := f.(map[string]interface{})
r := m["queryResult"].(map[string]interface{})
fmt.Println(r["outputContext"])
https://play.golang.com/p/gA5wWcPyd6E
Using struct:
type OutputContext struct {
Name string `json:"name"`
LifespanCount int `json:"lifespanCount"`
Parameters struct {
Name []string `json:"name"`
NameOriginal []string `json:"name.original"`
PhoneNumber string `json:"phone-number"`
PhoneNumberOriginal string `json:"phone-number.original"`
NoInput float64 `json:"no-input"`
NoMatch float64 `json:"no-match"`
} `json:"parameters"`
}
type QueryResult struct {
OutputContexts []OutputContext `json:"outputContexts"`
}
// ...
var dest struct {
QueryResult QueryResult `json:"queryResult"`
}
if err := json.Unmarshal(data, &dest); err != nil {
panic(err)
}
for _, v := range dest.QueryResult.OutputContexts {
fmt.Printf("%+v\n", v)
}
https://play.golang.com/p/EfIugDQJ651

Golang group and merge by value in goroutine

I'm new in go and tried to populate slice data by same values in GO.
Refer to the following example
input struct {
ID string `json:"id"`
Name string `json:"name"`
Image string `json:"image"`
}
output struct {
ID string `json:"id"`
Name string `json:"name"`
Image []img `json:"image"`
}
img struct {
Name string `json:"name"`
Width int `json:"width"`
Height int `json:"height"`
}
input = [{
"id": 10,
"name": "product 10",
"image": {"name": "https://i.imgur.com/eKSk6Fq.jpg"}
}, {
"id": 10,
"name": "product 10",
"image": {"name": "https://i.imgur.com/np1wmxw.jpg"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "https://i.imgur.com/jlFgGpe.jpg"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "https://i.imgur.com/B0D4iRk.jpg"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "https://i.imgur.com/4AiXzf8.jpg"}
}]
// expected output
output = [{
"id": 10,
"name": "product 10",
"image": [{
"name": "https://i.imgur.com/eKSk6Fq.jpg",
"width": 900,
"height": 600
}, {
"name": "https://i.imgur.com/np1wmxw.jpg",
"width": 600,
"height": 600
}]
}, {
"id": 11,
"name": "product 11",
"image": [{
"name": "https://i.imgur.com/jlFgGpe.jpg",
"width": 639,
"height": 700
}, {
"name": "https://i.imgur.com/B0D4iRk.jpg",
"width": 1280,
"height": 960
}, {
"name": "https://i.imgur.com/4AiXzf8.jpg",
"width": 540,
"height": 405
}]
}]
I would like to group input to a new slice based on the same ID,
so the result output would be new slice of new struct with grouped image with same ID.
H̶o̶w̶ ̶w̶o̶u̶l̶d̶ ̶I̶ ̶a̶c̶h̶i̶e̶v̶e̶d̶ ̶t̶h̶e̶ ̶̶o̶u̶t̶p̶u̶t̶̶ ̶r̶e̶s̶u̶l̶t̶ ̶u̶s̶i̶n̶g̶ ̶G̶O̶? update: got the answer from Peter Eichelsheim
Also, if I had to ge image size in the input with http.get and want to use goroutine, how would I achieve the result? since my last code here playground not achieving the correct output (always get the last input)
note: I don't know why I get null in go playground, but in my laptop the result is: [{"id":11,"name":"product 11","image":[{"name":"https://i.imgur.com/B0D4iRk.jpg","width":1280,"height":960}]}]
In PHP, I would do something below to achieve the intended output.
foreach ($input as $key => $value) {
if (!isset($output[$value["id"]])) {
$output[$value["id"]] = [
"id" => $value["id"],
"name" => $value["name"],
"image" => [],
];
}
$get = getimagesize($value["image"]["name"]);
if ($get) {
$width = isset($get[0]) ? $get[0] : 0;
$height = isset($get[1]) ? $get[1] : 0;
}
$output[$value["id"]]["image"][$key] = [
"name" => $value["image"]["name"],
"width" => $width,
"height" => $height,
];
$output[$value["id"]]["image"] = array_values($output[$value["id"]]["image"]);
}
$output = array_values($output);
$json = json_encode($output, true);
echo $json;
Thanks
Here a little sample with sample json input, using map[int]output to club images into the same product ID.
package main
import (
"encoding/json"
"fmt"
"log"
)
type input struct {
ID int `json:"id"`
Name string `json:"name"`
Image img `json:"image"`
}
type output struct {
ID int `json:"id"`
Name string `json:"name"`
Image []img `json:"image"`
}
type img struct {
Name string `json:"name"`
}
func main() {
var jsoninput = []byte(`
[{
"id": 10,
"name": "product 10",
"image": {"name": "image 10a"}
}, {
"id": 10,
"name": "product 10",
"image": {"name": "image 10b"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "image 11a"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "image 11b"}
}, {
"id": 11,
"name": "product 11",
"image": {"name": "image 11c"}
}]`)
var inputs []input
err := json.Unmarshal(jsoninput, &inputs)
if err != nil {
log.Fatalln("could not Unmarshal:", err)
}
var outputlist = make(map[int]output)
for _, inp := range inputs {
outputlist[inp.ID] = output{inp.ID, inp.Name, append(outputlist[inp.ID].Image, inp.Image)}
}
var outputs []output
for _, outp := range outputlist{
outputs = append(outputs,outp)
}
jsonoutput, err := json.Marshal(outputs)
fmt.Println(string(jsonoutput))
}
var inputs []input // assuming the json has been unmarshalled correctly
outs := make(map[int]output)
// this will create a map, keyed by id, values are groups of inputs
for _, input := range inputs {
out, exists := outs[input.ID]
if !exists {
out = output{
ID: input.ID,
Name: input.Name,
}
}
out.Image = append(out.Image, img{Name: input.Name})
}
output := make([]output, len(outs))
var idx int
for key, out := range outs {
output[idx] = out
idx++
}

Resources