diff --git a/24-structs/exercises/04-encode/main.go b/24-structs/exercises/04-encode/main.go new file mode 100644 index 0000000..ec36e9f --- /dev/null +++ b/24-structs/exercises/04-encode/main.go @@ -0,0 +1,65 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +// --------------------------------------------------------- +// EXERCISE: Encode +// +// Add a new command: "save". Encode the games to json, and +// print it, then terminate the loop. +// +// 1. Create a new struct type with exported fields: ID, Name, Genre and Price. +// +// 2. Create a new slice using the new struct type. +// +// 3. Save the games into the new slice. +// +// 4. Encode the new slice. +// +// +// RESTRICTION +// Do not export the fields of the game struct. +// +// +// EXPECTED OUTPUT +// Inanc's game store has 3 games. +// +// > list : lists all the games +// > id N : queries a game by id +// > save : exports the data to json and quits +// > quit : quits +// +// save +// +// [ +// { +// "id": 1, +// "name": "god of war", +// "genre": "action adventure", +// "price": 50 +// }, +// { +// "id": 2, +// "name": "x-com 2", +// "genre": "strategy", +// "price": 40 +// }, +// { +// "id": 3, +// "name": "minecraft", +// "genre": "sandbox", +// "price": 20 +// } +// ] +// +// --------------------------------------------------------- + +func main() { + // use your solution from the previous exercise + +} diff --git a/24-structs/exercises/04-encode/solution/main.go b/24-structs/exercises/04-encode/solution/main.go new file mode 100644 index 0000000..e9866c0 --- /dev/null +++ b/24-structs/exercises/04-encode/solution/main.go @@ -0,0 +1,126 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" +) + +func main() { + type item struct { + id int + name string + price int + } + + type game struct { + item + genre string + } + + games := []game{ + { + item: item{id: 1, name: "god of war", price: 50}, + genre: "action adventure", + }, + {item: item{id: 2, name: "x-com 2", price: 40}, genre: "strategy"}, + {item: item{id: 3, name: "minecraft", price: 20}, genre: "sandbox"}, + } + + // index the games by id + byID := make(map[int]game) + for _, g := range games { + byID[g.id] = g + } + + fmt.Printf("Inanc's game store has %d games.\n", len(games)) + + in := bufio.NewScanner(os.Stdin) + for { + fmt.Printf(` + > list : lists all the games + > id N : queries a game by id + > save : exports the data to json and quits + > quit : quits + +`) + + if !in.Scan() { + break + } + + fmt.Println() + + cmd := strings.Fields(in.Text()) + if len(cmd) == 0 { + continue + } + + switch cmd[0] { + case "quit": + fmt.Println("bye!") + return + + case "list": + for _, g := range games { + fmt.Printf("#%d: %-15q %-20s $%d\n", + g.id, g.name, "("+g.genre+")", g.price) + } + + case "id": + if len(cmd) != 2 { + fmt.Println("wrong id") + continue + } + + id, err := strconv.Atoi(cmd[1]) + if err != nil { + fmt.Println("wrong id") + continue + } + + g, ok := byID[id] + if !ok { + fmt.Println("sorry. i don't have the game") + continue + } + + fmt.Printf("#%d: %-15q %-20s $%d\n", + g.id, g.name, "("+g.genre+")", g.price) + + case "save": + type jsonGame struct { + ID int `json:"id"` + Name string `json:"name"` + Genre string `json:"genre"` + Price int `json:"price"` + } + + // load the data into the encodable game values + var encodable []jsonGame + for _, g := range games { + encodable = append(encodable, + jsonGame{g.id, g.name, g.genre, g.price}) + } + + out, err := json.MarshalIndent(encodable, "", "\t") + if err != nil { + fmt.Println("Sorry:", err) + continue + } + + fmt.Println(string(out)) + return + } + } +} diff --git a/24-structs/exercises/05-decode/main.go b/24-structs/exercises/05-decode/main.go new file mode 100644 index 0000000..6b51b12 --- /dev/null +++ b/24-structs/exercises/05-decode/main.go @@ -0,0 +1,57 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +// --------------------------------------------------------- +// EXERCISE: Decode +// +// At the beginning of the file: +// +// 1. Load the initial data to the game store from json. +// (see the data constant below) +// +// 2. Load the decoded values into the usual `game` values (to the games slice as well). +// +// So the rest of the program can work intact. +// +// +// HINT +// +// Move the jsonGame type to the top and reuse it both when +// loading the initial data, and in the "save" command. +// +// +// EXPECTED OUTPUT +// Please run the solution to see the output. +// --------------------------------------------------------- + +const data = ` +[ + { + "id": 1, + "name": "god of war", + "genre": "action adventure", + "price": 50 + }, + { + "id": 2, + "name": "x-com 2", + "genre": "strategy", + "price": 40 + }, + { + "id": 3, + "name": "minecraft", + "genre": "sandbox", + "price": 20 + } +]` + +func main() { + // use your solution from the previous exercise +} diff --git a/24-structs/exercises/05-decode/solution/main.go b/24-structs/exercises/05-decode/solution/main.go new file mode 100644 index 0000000..47e77cc --- /dev/null +++ b/24-structs/exercises/05-decode/solution/main.go @@ -0,0 +1,157 @@ +// For more tutorials: https://blog.learngoprogramming.com +// +// Copyright © 2018 Inanc Gumus +// Learn Go Programming Course +// License: https://creativecommons.org/licenses/by-nc-sa/4.0/ +// + +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" +) + +const data = ` +[ + { + "id": 1, + "name": "god of war", + "genre": "action adventure", + "price": 50 + }, + { + "id": 2, + "name": "x-com 2", + "genre": "strategy", + "price": 40 + }, + { + "id": 3, + "name": "minecraft", + "genre": "sandbox", + "price": 20 + } +]` + +func main() { + type item struct { + id int + name string + price int + } + + type game struct { + item + genre string + } + + // ---------------------------------------------------------- + // DECODING SOLUTION: + + // encodable and decodable game type + type jsonGame struct { + ID int `json:"id"` + Name string `json:"name"` + Genre string `json:"genre"` + Price int `json:"price"` + } + + // load the initial data from json + var decoded []jsonGame + if err := json.Unmarshal([]byte(data), &decoded); err != nil { + fmt.Println("Sorry, there is a problem:", err) + return + } + + // load the data into usual game values + var games []game + for _, dg := range decoded { + games = append(games, game{item{dg.ID, dg.Name, dg.Price}, dg.Genre}) + } + // ---------------------------------------------------------- + + // index the games by id + byID := make(map[int]game) + for _, g := range games { + byID[g.id] = g + } + + fmt.Printf("Inanc's game store has %d games.\n", len(games)) + + in := bufio.NewScanner(os.Stdin) + for { + fmt.Printf(` + > list : lists all the games + > id N : queries a game by id + > save : exports the data to json and quits + > quit : quits + +`) + + if !in.Scan() { + break + } + + fmt.Println() + + cmd := strings.Fields(in.Text()) + if len(cmd) == 0 { + continue + } + + switch cmd[0] { + case "quit": + fmt.Println("bye!") + return + + case "list": + for _, g := range games { + fmt.Printf("#%d: %-15q %-20s $%d\n", + g.id, g.name, "("+g.genre+")", g.price) + } + + case "id": + if len(cmd) != 2 { + fmt.Println("wrong id") + continue + } + + id, err := strconv.Atoi(cmd[1]) + if err != nil { + fmt.Println("wrong id") + continue + } + + g, ok := byID[id] + if !ok { + fmt.Println("sorry. i don't have the game") + continue + } + + fmt.Printf("#%d: %-15q %-20s $%d\n", + g.id, g.name, "("+g.genre+")", g.price) + + case "save": + // load the data into the encodable game values + var encodable []jsonGame + for _, g := range games { + encodable = append(encodable, + jsonGame{g.id, g.name, g.genre, g.price}) + } + + out, err := json.MarshalIndent(encodable, "", "\t") + if err != nil { + fmt.Println("Sorry:", err) + continue + } + + fmt.Println(string(out)) + return + } + } +} diff --git a/24-structs/exercises/README.md b/24-structs/exercises/README.md index 9daa642..c22e0ad 100644 --- a/24-structs/exercises/README.md +++ b/24-structs/exercises/README.md @@ -12,4 +12,12 @@ You'll build a queryable command-line game store. 3. **[Query By Id](https://github.com/inancgumus/learngo/tree/master/24-structs/exercises/03-query-by-id)** - Add a new command: "id". So the users can query the games by id. \ No newline at end of file + Add a new command: "id". So the users can query the games by id. + +4. **[Encode](https://github.com/inancgumus/learngo/tree/master/24-structs/exercises/04-encode)** + + Add a new command: "save". Encode the games to json, and print it, then terminate the loop. + +5. **[Decode](https://github.com/inancgumus/learngo/tree/master/24-structs/exercises/05-decode)** + + Load the initial data to the game store from json. \ No newline at end of file diff --git a/24-structs/questions/README.md b/24-structs/questions/README.md index d9050c7..da9f4cc 100644 --- a/24-structs/questions/README.md +++ b/24-structs/questions/README.md @@ -176,3 +176,55 @@ fmt.Println(m.title, "&", m.item.title) > **4:** Right! `m.title` returns "avengers: end game" because the outer type always takes priority. However, `m.item.title` returns "midnight in paris" because you explicitly get it from the inner type: item. > + +## What is a field tag? +1. It allows Go to index struct fields more efficiently +2. You can use it for documenting your code +3. It's like a comment +4. Associates metadata about the field *CORRECT* + +> **4:** Correct. For example, the json package can read and encode/decode depending on the associated metadata. + + +## Which one is correct about a field tag? +1. It needs to be typed according to some rules +2. You can change it to a different value in runtime +3. It's just a string value, and it doesn't have a meaning on its own *CORRECT* + +> **1:** This is true to some extent but it can have any value. +> +> **2:** Fields tags are part of a struct type definition so you cannot change their value in runtime. +> +> **3:** Right! It's just a string value. It's only meaningful when other code reads it. For example, the json package can read it and encode/decode depending on the field tag's value. +> + + +## What is wrong with the following program? +```go +type movie struct { + title string `json:"title"` +} + +m := movie{"black panthers"} +encoded, _ := json.Marshal(m) + +fmt.Println(string(encoded)) +``` +1. `movie` is unexported so you cannot encode +2. `title` is unexported so you cannot encode *CORRECT* +3. Error handling is missing so you cannot encode + +> **1:** The json package can encode a struct even though its type is unexported. +> +> **2:** Right! The json package can only encode exported fields. +> +> **3:** It's better to handle errors but it's not the main problem here. +> + + +## Why do you need to pass a pointer to the Unmarshal function? +1. To make it work faster and efficient +2. So it can update the value on memory *CORRECT* +3. To prevent errors + +> **2:** Otherwise, it would not be able to update the given value. It's because, every value in Go is passed by value. So a function can only change the copy, not the original value. However, through a pointer, a function can change the original value. \ No newline at end of file