From 33aead27216f6c6a9263b6102bc857de62a29d0b Mon Sep 17 00:00:00 2001 From: Franceskynov Date: Tue, 31 Dec 2024 00:35:57 -0600 Subject: [PATCH 01/10] Add new entry: working with JSON in Go --- .gitignore | 1 + .../working-with-json/working-with-json.md | 228 ++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 content/go/concepts/working-with-json/working-with-json.md diff --git a/.gitignore b/.gitignore index e9213a1a10a..8e75026beff 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ yarn-error.log* *.swp *.swo tmp +pnpm-lock.yaml \ No newline at end of file diff --git a/content/go/concepts/working-with-json/working-with-json.md b/content/go/concepts/working-with-json/working-with-json.md new file mode 100644 index 00000000000..c44811fd4af --- /dev/null +++ b/content/go/concepts/working-with-json/working-with-json.md @@ -0,0 +1,228 @@ +--- +Title: 'Working with JSON' +Description: 'Working with JSON files, requests, or responses is an essential part of interacting with data that will be used to exchange information between the Frontend and Backend in web applications.' +Subjects: + - 'Code Foundations' + - 'Computer Science' +Tags: + - 'JSON' + - 'Marshal' + - 'Unmarshal' + - 'Encode' + - 'Decode' +CatalogContent: + - 'learn-go' + - 'paths/back-end-engineer-career-path' +--- + +**JSON** is one of the most commonly used data interchange formats for communication between web applications, other services, mobile applications, and so on. +In Go there are functions dedicated to work with **JSON**, here we are going to describe the most used. + +| Feature | json.Marshal | json.NewDecoder | +| ------------ | ----------------------------------------------------------------------------- | ------------------------------------------------ | +| Input | Go value (struct, map, etc.) | io.ReadCloser (network connection) | +| Output | JSON byte slice | Go value (struct, map, etc.) | +| Memory Usage | Can potentially use more memory if the input data is large | Generally more memory-efficient for large inputs | +| Use Cases | Converting Go data to JSON for various purposes, suitable for small data sets | Decoding JSON data from streams or large files | + +## Marshal and Unmarshal + +In Go `json.Unmarshall` is used to decode JSON data formatted as a string. This allows us to add new elements, extract specific values like configurations, and modify existing ones. Subsequently, Next is `json.Marshal` a function that is used to encode the modified Go data structure into a string, which can then be saved to a file or used for other purposes, as demonstrated in the example below: + +```go +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" +) + +// We need to define a struct that mirrors the structure of the JSON data we want to unmarshal. +type Meal struct { + Name string `json:"name"` + Ingredients []string `json:"ingredients"` +} + +type Meals struct { + Meals []Meal `json:"meals"` +} + +func main() { + menu := `{ + "meals": [ + {"name": "Pizza Margherita", "ingredients": ["Dough", "Tomato Sauce", "Mozzarella Cheese", "Tomato", "Basil"]}, + {"name": "Spaghetti Carbonara", "ingredients": ["Spaghetti", "Eggs", "Pancetta", "Pecorino Romano Cheese", "Black Pepper"]}, + {"name": "Chicken Stir-fry", "ingredients": ["Chicken", "Vegetables (e.g., Broccoli, Carrots, Onions, Peppers)", "Rice", "Soy Sauce", "Ginger", "Garlic"]}, + {"name": "Tacos al Pastor", "ingredients": ["Pork", "Pineapple", "Tortillas", "Onion", "Cilantro"]}, + {"name": "Caesar Salad", "ingredients": ["Romaine Lettuce", "Croutons", "Parmesan Cheese", "Caesar Dressing"]} + ] + }` + + // We need a variable to store the data decoded from the unmarshaling process. + meals := Meals{} + + // To unmarshal the data, we need to provide: + // 1. Data in the format of a slice of bits. + // 2. A pointer to the location where the data will be stored. + if err := json.Unmarshal([]byte(menu), &meals); err != nil { + // Handle the errors in case they occur. + fmt.Println(err) + } + + // In case we need to add a new object to the + meal := Meal{ + Name: "Pupusas", + Ingredients: []string{"Cheese", "Refried beans", "Tomato Sauce", "Rice flour", "Pickled cabbage"}, + } + + // Add new meal for the menu + meals.Meals = append(meals.Meals, meal) + + // Add new meal in line for the menu + meals.Meals = append(meals.Meals, Meal{Name: "Sushi", Ingredients: []string{"Rice", "Fish", "Seaweed", "Wasabi", "Soy Sauce", "Ginger"}}) + + // List the menu of meals + for _, meal := range meals.Meals { + fmt.Printf("---\nMeal: %s\ningredients: %s \n", meal.Name, strings.Join(meal.Ingredients, ", ")) + } + + // Serialize the object to a slice of bytes + data, err := json.Marshal(meals) + if err != nil { + fmt.Println(err) + } + fmt.Println("-------------") + fmt.Println(string(data)) + + // Crate an empty file + file, err := os.Create("meals.json") + if err != nil { + fmt.Println("Error opening file:", err) + return + } + + defer file.Close() + + // Write data into the file previously created + if _, err = file.Write(data); err != nil { + fmt.Println(err) + return + } + +} +``` + +## NewEncoder and NewDecoder + +In Go `json.NewDecoder` is used to format data received from a REST API. Other function that is commonly used is `json.NewEncoder` to exchange data between backend side and external clients that require a JSON response. + +In the following example, we will demonstrate the usage of both functions. We will make a call to an external REST API and enhance the result by adding products made from the fruit specified in the query parameter. For example: `http://localhost:4444/fruit?name=Strawberry` + +```go +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "strings" +) + +type ProductsByFruit map[string][]string + +type Fruit struct { + Name string `json:"name"` + ID int `json:"id"` + Family string `json:"family"` + Order string `json:"order"` + Genus string `json:"genus"` + Products []string `json:"products"` +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc("POST /fruit", func(w http.ResponseWriter, r *http.Request) { + + // Get query params from URL + params := r.URL.Query() + + // We are going to validate the name of the fruit + if fruitName := params.Get("name"); len(fruitName) > 0 { + + // Set the specific content type for the response + w.Header().Set("Content-Type", "application/json") + + // Get the results of the invocation to the REST API. + result, err := GetFruitData(fruitName) + + // We need to handle any errors that occur during the decoding process. + if err != nil { + http.Error(w, "Error fruit data not found", http.StatusNotFound) + return + } + + // We need a variable to store the results from the decoding process. + fruit := Fruit{} + + // To Decode the data, we need to provide: + // 1. Data in the format of io.ReadCloser. + // 2. A pointer to the location where the data will be stored. + err = json.NewDecoder(result).Decode(&fruit) + + // We need to handle any errors that occur during the decoding process. + if err != nil { + http.Error(w, "Error on decoding data", http.StatusInternalServerError) + return + } + + // We need a map of products, keyed by fruit, to enhance the results of the external REST API. + productsByFruit := ProductsByFruit{ + "strawberry": {"Smoothies", "Ice cream", "Jelly"}, + "banana": {"Banana split", "Smoothies"}, + "tomato": {"Salads", "Sauces"}, + "raspberry": {"Pies", "Smoothies"}, + "orange": {"Juice", "Jelly"}, + "blueberry": {"Smoothies", "Pies"}, + "pumpkin": {"Pies", "Late"}, + } + + // Set the products by fruit + fruit.Products = productsByFruit[strings.ToLower(fruitName)] + + // Other way to handle any errors. + if err := json.NewEncoder(w).Encode(fruit); err != nil { + http.Error(w, "Error on encoding data", http.StatusInternalServerError) + return + } + + } else { + http.Error(w, "Bad request, query param ?name= required", http.StatusBadRequest) + return + } + }) + + if err := http.ListenAndServe(":4444", mux); err != nil { + log.Fatal("Error occurred while starting the server: ", err) + } +} + +func GetFruitData(name string) (io.ReadCloser, error) { + // Retrieve data form external REST API. + response, err := http.Get(fmt.Sprintf("https://www.fruityvice.com/api/fruit/%s", name)) + + // Handle errors + switch response.StatusCode { + case http.StatusOK: + return response.Body, err + case http.StatusNotFound: + return nil, errors.New("is the fruit name you entered correct") + default: + return nil, fmt.Errorf("errors on the server with status: %d", response.StatusCode) + } +} +``` From 3ebc4a45e80e51cfccf45230c986248e763ae6d6 Mon Sep 17 00:00:00 2001 From: Pragati Verma Date: Fri, 10 Jan 2025 19:18:40 +0530 Subject: [PATCH 02/10] Update working-with-json.md --- content/go/concepts/working-with-json/working-with-json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/go/concepts/working-with-json/working-with-json.md b/content/go/concepts/working-with-json/working-with-json.md index c44811fd4af..1b3931aa8b5 100644 --- a/content/go/concepts/working-with-json/working-with-json.md +++ b/content/go/concepts/working-with-json/working-with-json.md @@ -1,6 +1,6 @@ --- Title: 'Working with JSON' -Description: 'Working with JSON files, requests, or responses is an essential part of interacting with data that will be used to exchange information between the Frontend and Backend in web applications.' +Description: 'Working with JSON files, requests, or responses is essential to interacting with data that is exchanged between the Frontend and Backend in web applications.' Subjects: - 'Code Foundations' - 'Computer Science' From 841227b8599bad02eebfd3528542b6d1044d5f71 Mon Sep 17 00:00:00 2001 From: Pragati Verma Date: Fri, 10 Jan 2025 19:18:53 +0530 Subject: [PATCH 03/10] Update working-with-json.md --- content/go/concepts/working-with-json/working-with-json.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/content/go/concepts/working-with-json/working-with-json.md b/content/go/concepts/working-with-json/working-with-json.md index 1b3931aa8b5..e1aa8373091 100644 --- a/content/go/concepts/working-with-json/working-with-json.md +++ b/content/go/concepts/working-with-json/working-with-json.md @@ -15,8 +15,9 @@ CatalogContent: - 'paths/back-end-engineer-career-path' --- -**JSON** is one of the most commonly used data interchange formats for communication between web applications, other services, mobile applications, and so on. -In Go there are functions dedicated to work with **JSON**, here we are going to describe the most used. +**JSON** is one of the most commonly used data exchange formats for communication between web applications, other services, mobile applications, etc. + +Go provides built-in functions to work with JSON effectively. Below are some of the most commonly used functions: | Feature | json.Marshal | json.NewDecoder | | ------------ | ----------------------------------------------------------------------------- | ------------------------------------------------ | From ea910b372187feb75f5fae52a129c2df53835d44 Mon Sep 17 00:00:00 2001 From: Pragati Verma Date: Fri, 10 Jan 2025 19:19:05 +0530 Subject: [PATCH 04/10] Update working-with-json.md --- content/go/concepts/working-with-json/working-with-json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/go/concepts/working-with-json/working-with-json.md b/content/go/concepts/working-with-json/working-with-json.md index e1aa8373091..b44afb7a994 100644 --- a/content/go/concepts/working-with-json/working-with-json.md +++ b/content/go/concepts/working-with-json/working-with-json.md @@ -21,7 +21,7 @@ Go provides built-in functions to work with JSON effectively. Below are some of | Feature | json.Marshal | json.NewDecoder | | ------------ | ----------------------------------------------------------------------------- | ------------------------------------------------ | -| Input | Go value (struct, map, etc.) | io.ReadCloser (network connection) | +| Input | Go value (`struct`, `map`, etc.) | `io.ReadCloser` (network connection) | | Output | JSON byte slice | Go value (struct, map, etc.) | | Memory Usage | Can potentially use more memory if the input data is large | Generally more memory-efficient for large inputs | | Use Cases | Converting Go data to JSON for various purposes, suitable for small data sets | Decoding JSON data from streams or large files | From 237104a598764c2b9805fe0472633078f63f67c0 Mon Sep 17 00:00:00 2001 From: Pragati Verma Date: Fri, 10 Jan 2025 19:19:19 +0530 Subject: [PATCH 05/10] Update working-with-json.md --- content/go/concepts/working-with-json/working-with-json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/go/concepts/working-with-json/working-with-json.md b/content/go/concepts/working-with-json/working-with-json.md index b44afb7a994..b0054d5d031 100644 --- a/content/go/concepts/working-with-json/working-with-json.md +++ b/content/go/concepts/working-with-json/working-with-json.md @@ -19,7 +19,7 @@ CatalogContent: Go provides built-in functions to work with JSON effectively. Below are some of the most commonly used functions: -| Feature | json.Marshal | json.NewDecoder | +| Feature | `json.Marshal` | `json.NewDecoder` | | ------------ | ----------------------------------------------------------------------------- | ------------------------------------------------ | | Input | Go value (`struct`, `map`, etc.) | `io.ReadCloser` (network connection) | | Output | JSON byte slice | Go value (struct, map, etc.) | From ea9b318ef985dd6868b79dc84eb954741824faa3 Mon Sep 17 00:00:00 2001 From: Pragati Verma Date: Fri, 10 Jan 2025 19:19:33 +0530 Subject: [PATCH 06/10] Update working-with-json.md --- content/go/concepts/working-with-json/working-with-json.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/content/go/concepts/working-with-json/working-with-json.md b/content/go/concepts/working-with-json/working-with-json.md index b0054d5d031..76f4b317272 100644 --- a/content/go/concepts/working-with-json/working-with-json.md +++ b/content/go/concepts/working-with-json/working-with-json.md @@ -28,7 +28,10 @@ Go provides built-in functions to work with JSON effectively. Below are some of ## Marshal and Unmarshal -In Go `json.Unmarshall` is used to decode JSON data formatted as a string. This allows us to add new elements, extract specific values like configurations, and modify existing ones. Subsequently, Next is `json.Marshal` a function that is used to encode the modified Go data structure into a string, which can then be saved to a file or used for other purposes, as demonstrated in the example below: + +In Go, `json.Marshal` encodes a Go data structure into a JSON-formatted string. This encoded data can then be saved to a file, transmitted, or used in other applications. + +Conversely, `json.Unmarshal` decodes JSON data into a Go data structure. This allows for operations such as adding new elements, extracting specific values (e.g., configurations), or modifying existing ones, as demonstrated in the example below: ```go package main From 6268522fda7899eeb9ad582e6cc63e3f9e1c3c26 Mon Sep 17 00:00:00 2001 From: PragatiVerma18 Date: Fri, 10 Jan 2025 19:39:00 +0530 Subject: [PATCH 07/10] Review Fixes --- .gitignore | 1 - .../working-with-json/working-with-json.md | 205 +++++++++++++----- 2 files changed, 146 insertions(+), 60 deletions(-) diff --git a/.gitignore b/.gitignore index 8e75026beff..e9213a1a10a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,3 @@ yarn-error.log* *.swp *.swo tmp -pnpm-lock.yaml \ No newline at end of file diff --git a/content/go/concepts/working-with-json/working-with-json.md b/content/go/concepts/working-with-json/working-with-json.md index 76f4b317272..b27587106b1 100644 --- a/content/go/concepts/working-with-json/working-with-json.md +++ b/content/go/concepts/working-with-json/working-with-json.md @@ -6,10 +6,9 @@ Subjects: - 'Computer Science' Tags: - 'JSON' - - 'Marshal' - - 'Unmarshal' - - 'Encode' - - 'Decode' + - 'Data' + - 'Development' + - 'Objects' CatalogContent: - 'learn-go' - 'paths/back-end-engineer-career-path' @@ -17,21 +16,33 @@ CatalogContent: **JSON** is one of the most commonly used data exchange formats for communication between web applications, other services, mobile applications, etc. -Go provides built-in functions to work with JSON effectively. Below are some of the most commonly used functions: +Go provides built-in functions to work with JSON effectively. -| Feature | `json.Marshal` | `json.NewDecoder` | -| ------------ | ----------------------------------------------------------------------------- | ------------------------------------------------ | -| Input | Go value (`struct`, `map`, etc.) | `io.ReadCloser` (network connection) | -| Output | JSON byte slice | Go value (struct, map, etc.) | -| Memory Usage | Can potentially use more memory if the input data is large | Generally more memory-efficient for large inputs | -| Use Cases | Converting Go data to JSON for various purposes, suitable for small data sets | Decoding JSON data from streams or large files | +## Using Marshal and Unmarshal -## Marshal and Unmarshal +In Go, `json.Marshal` encodes a Go data structure into a JSON-formatted string. This encoded data can then be saved to a file, transmitted, or used in other applications. +The syntax of `json.Marshal` looks like this: -In Go, `json.Marshal` encodes a Go data structure into a JSON-formatted string. This encoded data can then be saved to a file, transmitted, or used in other applications. +```pseudo +data, err := json.Marshal(v) +``` + +- `v`: The Go value to be marshaled (e.g., `struct`, `map`). +- `data`: The result of the marshaling, returned as a byte slice (`[]byte`). +- `err`: An error, if any, that occurred during marshaling. -Conversely, `json.Unmarshal` decodes JSON data into a Go data structure. This allows for operations such as adding new elements, extracting specific values (e.g., configurations), or modifying existing ones, as demonstrated in the example below: +Conversely, `json.Unmarshal` decodes JSON data into a Go data structure. The syntax for `json.Unmarshal` looks like this: + +```pseudo +err := json.Unmarshal(data, &v) +``` + +- `data`: The JSON byte slice to be unmarshaled. +- `v`: The Go value (typically a pointer to a `struct` or `map`) where the unmarshaled data will be stored. +- `err`: An error, if any, that occurred during unmarshaling. + +This allows for operations such as adding new elements, extracting specific values (e.g., configurations), or modifying existing ones, as demonstrated in the example below: ```go package main @@ -43,7 +54,7 @@ import ( "strings" ) -// We need to define a struct that mirrors the structure of the JSON data we want to unmarshal. +// Define a struct that mirrors the structure of the JSON data we want to unmarshal. type Meal struct { Name string `json:"name"` Ingredients []string `json:"ingredients"` @@ -64,63 +75,127 @@ func main() { ] }` - // We need a variable to store the data decoded from the unmarshaling process. + // Declare a variable to store the decoded data. meals := Meals{} - // To unmarshal the data, we need to provide: - // 1. Data in the format of a slice of bits. - // 2. A pointer to the location where the data will be stored. + // Unmarshal the JSON string into the `meals` struct. if err := json.Unmarshal([]byte(menu), &meals); err != nil { - // Handle the errors in case they occur. - fmt.Println(err) + fmt.Println("Error unmarshalling data:", err) + return } - // In case we need to add a new object to the + // Add a new meal to the menu. meal := Meal{ Name: "Pupusas", Ingredients: []string{"Cheese", "Refried beans", "Tomato Sauce", "Rice flour", "Pickled cabbage"}, } - // Add new meal for the menu + // Add the new meal to the list. meals.Meals = append(meals.Meals, meal) - // Add new meal in line for the menu + // Add another meal inline. meals.Meals = append(meals.Meals, Meal{Name: "Sushi", Ingredients: []string{"Rice", "Fish", "Seaweed", "Wasabi", "Soy Sauce", "Ginger"}}) - // List the menu of meals + // List the menu of meals. for _, meal := range meals.Meals { - fmt.Printf("---\nMeal: %s\ningredients: %s \n", meal.Name, strings.Join(meal.Ingredients, ", ")) + fmt.Printf("---\nMeal: %s\nIngredients: %s\n", meal.Name, strings.Join(meal.Ingredients, ", ")) } - // Serialize the object to a slice of bytes + // Serialize the object to a slice of bytes. data, err := json.Marshal(meals) if err != nil { - fmt.Println(err) + fmt.Println("Error marshalling data:", err) + return } + fmt.Println("-------------") fmt.Println(string(data)) - // Crate an empty file + // Create an empty file. file, err := os.Create("meals.json") if err != nil { - fmt.Println("Error opening file:", err) + fmt.Println("Error creating file:", err) return } + // Ensure the file is closed after the program finishes. defer file.Close() - // Write data into the file previously created + // Write the serialized data into the file. if _, err = file.Write(data); err != nil { - fmt.Println(err) + fmt.Println("Error writing to file:", err) return } + fmt.Println("Data has been written to meals.json successfully.") +} +``` + +When you run the above Go program, the following will happen: + +- The menu of meals will be printed to the console. +- The serialized JSON data will be printed to the console. +- A `meals.json` file will be created in the working directory with the serialized data. + +Here's how the console output will look like: + +```shell +--- +Meal: Pizza Margherita +Ingredients: Dough, Tomato Sauce, Mozzarella Cheese, Tomato, Basil +--- +Meal: Spaghetti Carbonara +Ingredients: Spaghetti, Eggs, Pancetta, Pecorino Romano Cheese, Black Pepper +--- +Meal: Chicken Stir-fry +Ingredients: Chicken, Vegetables (e.g., Broccoli, Carrots, Onions, Peppers), Rice, Soy Sauce, Ginger, Garlic +--- +Meal: Tacos al Pastor +Ingredients: Pork, Pineapple, Tortillas, Onion, Cilantro +--- +Meal: Caesar Salad +Ingredients: Romaine Lettuce, Croutons, Parmesan Cheese, Caesar Dressing +--- +Meal: Pupusas +Ingredients: Cheese, Refried beans, Tomato Sauce, Rice flour, Pickled cabbage +--- +Meal: Sushi +Ingredients: Rice, Fish, Seaweed, Wasabi, Soy Sauce, Ginger +------------- +{"meals":[{"name":"Pizza Margherita","ingredients":["Dough","Tomato Sauce","Mozzarella Cheese","Tomato","Basil"]},{"name":"Spaghetti Carbonara","ingredients":["Spaghetti","Eggs","Pancetta","Pecorino Romano Cheese","Black Pepper"]},{"name":"Chicken Stir-fry","ingredients":["Chicken","Vegetables (e.g., Broccoli, Carrots, Onions, Peppers)","Rice","Soy Sauce","Ginger","Garlic"]},{"name":"Tacos al Pastor","ingredients":["Pork","Pineapple","Tortillas","Onion","Cilantro"]},{"name":"Caesar Salad","ingredients":["Romaine Lettuce","Croutons","Parmesan Cheese","Caesar Dressing"]},{"name":"Pupusas","ingredients":["Cheese","Refried beans","Tomato Sauce","Rice flour","Pickled cabbage"]},{"name":"Sushi","ingredients":["Rice","Fish","Seaweed","Wasabi","Soy Sauce","Ginger"]}] } +Data has been written to meals.json successfully. ``` -## NewEncoder and NewDecoder +## Using NewEncoder and NewDecoder + +In Go, `json.NewEncoder` is commonly used to encode data into a JSON format, making it suitable for exchanging information between the backend and external clients that require a JSON response. + +The syntax for `json.NewEncoder` looks like this: + +```pseudo +encoder := json.NewEncoder(w) +err := encoder.Encode(v) +``` + +- `w`: The `io.Writer` (e.g., `http.ResponseWriter`, file, etc.) where the JSON output will be written. +- `v`: The Go value (typically a `struct`, `map`, etc.) to be encoded into JSON. +- `encoder`: A new `json.Encoder` instance that is used to encode the Go value. +- `err`: An error, if any, that occurred during encoding. + +On the other hand, `json.NewDecoder` is used to decode data received from a REST API, transforming it into a usable Go data structure. -In Go `json.NewDecoder` is used to format data received from a REST API. Other function that is commonly used is `json.NewEncoder` to exchange data between backend side and external clients that require a JSON response. +The syntax for `json.NewDecoder` looks like this: + +```pseudo +decoder := json.NewDecoder(r) +err := decoder.Decode(v) +``` + +- `r`: The `io.Reader` (e.g., `http.Request.Body`, file) containing the JSON data to be decoded. +- `v`: A pointer to the Go value (typically a `struct`, `map`, etc.) where the decoded data will be stored. +- `decoder`: A new `json.Decoder` instance that is used to decode the JSON data. +- `err`: An error, if any, that occurred during decoding. In the following example, we will demonstrate the usage of both functions. We will make a call to an external REST API and enhance the result by adding products made from the fruit specified in the query parameter. For example: `http://localhost:4444/fruit?name=Strawberry` @@ -150,41 +225,37 @@ type Fruit struct { func main() { mux := http.NewServeMux() - mux.HandleFunc("POST /fruit", func(w http.ResponseWriter, r *http.Request) { + mux.HandleFunc("/fruit", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } // Get query params from URL params := r.URL.Query() - // We are going to validate the name of the fruit + // Validate the name of the fruit if fruitName := params.Get("name"); len(fruitName) > 0 { - // Set the specific content type for the response + // Set the content type for the response w.Header().Set("Content-Type", "application/json") - // Get the results of the invocation to the REST API. + // Get the results of the invocation to the REST API result, err := GetFruitData(fruitName) - - // We need to handle any errors that occur during the decoding process. if err != nil { - http.Error(w, "Error fruit data not found", http.StatusNotFound) + http.Error(w, fmt.Sprintf("Failed to get fruit data: %v", err), http.StatusInternalServerError) return } - // We need a variable to store the results from the decoding process. + // Decode the response into the fruit struct fruit := Fruit{} - - // To Decode the data, we need to provide: - // 1. Data in the format of io.ReadCloser. - // 2. A pointer to the location where the data will be stored. err = json.NewDecoder(result).Decode(&fruit) - - // We need to handle any errors that occur during the decoding process. if err != nil { - http.Error(w, "Error on decoding data", http.StatusInternalServerError) + http.Error(w, "Error decoding data", http.StatusInternalServerError) return } - // We need a map of products, keyed by fruit, to enhance the results of the external REST API. + // Enhance the fruit data with products productsByFruit := ProductsByFruit{ "strawberry": {"Smoothies", "Ice cream", "Jelly"}, "banana": {"Banana split", "Smoothies"}, @@ -192,15 +263,14 @@ func main() { "raspberry": {"Pies", "Smoothies"}, "orange": {"Juice", "Jelly"}, "blueberry": {"Smoothies", "Pies"}, - "pumpkin": {"Pies", "Late"}, + "pumpkin": {"Pies", "Latte"}, } - // Set the products by fruit fruit.Products = productsByFruit[strings.ToLower(fruitName)] - // Other way to handle any errors. + // Encode the modified fruit struct to the response if err := json.NewEncoder(w).Encode(fruit); err != nil { - http.Error(w, "Error on encoding data", http.StatusInternalServerError) + http.Error(w, "Error encoding data", http.StatusInternalServerError) return } @@ -216,17 +286,34 @@ func main() { } func GetFruitData(name string) (io.ReadCloser, error) { - // Retrieve data form external REST API. + // Retrieve data from external REST API response, err := http.Get(fmt.Sprintf("https://www.fruityvice.com/api/fruit/%s", name)) + if err != nil { + return nil, fmt.Errorf("failed to make request: %v", err) + } + if response == nil { + return nil, errors.New("no response from the server") + } - // Handle errors + // Handle different HTTP status codes switch response.StatusCode { case http.StatusOK: - return response.Body, err + return response.Body, nil case http.StatusNotFound: - return nil, errors.New("is the fruit name you entered correct") + return nil, errors.New("fruit not found") default: - return nil, fmt.Errorf("errors on the server with status: %d", response.StatusCode) + return nil, fmt.Errorf("server error with status: %d", response.StatusCode) } } ``` + +## Comparison between `json.Marshal` and `json.NewDecoder` + +Below is a comparison between `json.Marshal` and `json.NewDecoder` based on their use cases and performance considerations: + +| Feature | `json.Marshal` | `json.NewDecoder` | +| ------------ | ----------------------------------------------------------------------------- | ------------------------------------------------ | +| Input | Go value (`struct`, `map`, etc.) | `io.ReadCloser` (network connection) | +| Output | JSON byte slice | Go value (struct, map, etc.) | +| Memory Usage | Can potentially use more memory if the input data is large | Generally more memory-efficient for large inputs | +| Use Cases | Converting Go data to JSON for various purposes, suitable for small data sets | Decoding JSON data from streams or large files | From e3f24adb247f1511db5cda268e4764ef50ea9871 Mon Sep 17 00:00:00 2001 From: Avdhoot Fulsundar Date: Sat, 18 Jan 2025 23:25:57 +0530 Subject: [PATCH 08/10] changes --- .../working-with-json/working-with-json.md | 337 +++++++++--------- 1 file changed, 177 insertions(+), 160 deletions(-) diff --git a/content/go/concepts/working-with-json/working-with-json.md b/content/go/concepts/working-with-json/working-with-json.md index b27587106b1..74e4c3cd62c 100644 --- a/content/go/concepts/working-with-json/working-with-json.md +++ b/content/go/concepts/working-with-json/working-with-json.md @@ -48,86 +48,86 @@ This allows for operations such as adding new elements, extracting specific valu package main import ( - "encoding/json" - "fmt" - "os" - "strings" + "encoding/json" + "fmt" + "os" + "strings" ) // Define a struct that mirrors the structure of the JSON data we want to unmarshal. type Meal struct { - Name string `json:"name"` - Ingredients []string `json:"ingredients"` + Name string `json:"name"` + Ingredients []string `json:"ingredients"` } type Meals struct { - Meals []Meal `json:"meals"` + Meals []Meal `json:"meals"` } func main() { - menu := `{ - "meals": [ - {"name": "Pizza Margherita", "ingredients": ["Dough", "Tomato Sauce", "Mozzarella Cheese", "Tomato", "Basil"]}, - {"name": "Spaghetti Carbonara", "ingredients": ["Spaghetti", "Eggs", "Pancetta", "Pecorino Romano Cheese", "Black Pepper"]}, - {"name": "Chicken Stir-fry", "ingredients": ["Chicken", "Vegetables (e.g., Broccoli, Carrots, Onions, Peppers)", "Rice", "Soy Sauce", "Ginger", "Garlic"]}, - {"name": "Tacos al Pastor", "ingredients": ["Pork", "Pineapple", "Tortillas", "Onion", "Cilantro"]}, - {"name": "Caesar Salad", "ingredients": ["Romaine Lettuce", "Croutons", "Parmesan Cheese", "Caesar Dressing"]} - ] - }` - - // Declare a variable to store the decoded data. - meals := Meals{} - - // Unmarshal the JSON string into the `meals` struct. - if err := json.Unmarshal([]byte(menu), &meals); err != nil { - fmt.Println("Error unmarshalling data:", err) - return - } - - // Add a new meal to the menu. - meal := Meal{ - Name: "Pupusas", - Ingredients: []string{"Cheese", "Refried beans", "Tomato Sauce", "Rice flour", "Pickled cabbage"}, - } - - // Add the new meal to the list. - meals.Meals = append(meals.Meals, meal) - - // Add another meal inline. - meals.Meals = append(meals.Meals, Meal{Name: "Sushi", Ingredients: []string{"Rice", "Fish", "Seaweed", "Wasabi", "Soy Sauce", "Ginger"}}) - - // List the menu of meals. - for _, meal := range meals.Meals { - fmt.Printf("---\nMeal: %s\nIngredients: %s\n", meal.Name, strings.Join(meal.Ingredients, ", ")) - } - - // Serialize the object to a slice of bytes. - data, err := json.Marshal(meals) - if err != nil { - fmt.Println("Error marshalling data:", err) - return - } - - fmt.Println("-------------") - fmt.Println(string(data)) - - // Create an empty file. - file, err := os.Create("meals.json") - if err != nil { - fmt.Println("Error creating file:", err) - return - } - - // Ensure the file is closed after the program finishes. - defer file.Close() - - // Write the serialized data into the file. - if _, err = file.Write(data); err != nil { - fmt.Println("Error writing to file:", err) - return - } - - fmt.Println("Data has been written to meals.json successfully.") + menu := `{ + "meals": [ + {"name": "Pizza Margherita", "ingredients": ["Dough", "Tomato Sauce", "Mozzarella Cheese", "Tomato", "Basil"]}, + {"name": "Spaghetti Carbonara", "ingredients": ["Spaghetti", "Eggs", "Pancetta", "Pecorino Romano Cheese", "Black Pepper"]}, + {"name": "Chicken Stir-fry", "ingredients": ["Chicken", "Vegetables (e.g., Broccoli, Carrots, Onions, Peppers)", "Rice", "Soy Sauce", "Ginger", "Garlic"]}, + {"name": "Tacos al Pastor", "ingredients": ["Pork", "Pineapple", "Tortillas", "Onion", "Cilantro"]}, + {"name": "Caesar Salad", "ingredients": ["Romaine Lettuce", "Croutons", "Parmesan Cheese", "Caesar Dressing"]} + ] + }` + + // Declare a variable to store the decoded data. + meals := Meals{} + + // Unmarshal the JSON string into the `meals` struct. + if err := json.Unmarshal([]byte(menu), &meals); err != nil { + fmt.Println("Error unmarshalling data:", err) + return + } + + // Add a new meal to the menu. + meal := Meal{ + Name: "Pupusas", + Ingredients: []string{"Cheese", "Refried beans", "Tomato Sauce", "Rice flour", "Pickled cabbage"}, + } + + // Add the new meal to the list. + meals.Meals = append(meals.Meals, meal) + + // Add another meal inline. + meals.Meals = append(meals.Meals, Meal{Name: "Sushi", Ingredients: []string{"Rice", "Fish", "Seaweed", "Wasabi", "Soy Sauce", "Ginger"}}) + + // List the menu of meals. + for _, meal := range meals.Meals { + fmt.Printf("---\nMeal: %s\nIngredients: %s\n", meal.Name, strings.Join(meal.Ingredients, ", ")) + } + + // Serialize the object to a slice of bytes. + data, err := json.Marshal(meals) + if err != nil { + fmt.Println("Error marshalling data:", err) + return + } + + fmt.Println("-------------") + fmt.Println(string(data)) + + // Create an empty file. + file, err := os.Create("meals.json") + if err != nil { + fmt.Println("Error creating file:", err) + return + } + + // Ensure the file is closed after the program finishes. + defer file.Close() + + // Write the serialized data into the file. + if _, err = file.Write(data); err != nil { + fmt.Println("Error writing to file:", err) + return + } + + fmt.Println("Data has been written to meals.json successfully.") } ``` @@ -197,116 +197,133 @@ err := decoder.Decode(v) - `decoder`: A new `json.Decoder` instance that is used to decode the JSON data. - `err`: An error, if any, that occurred during decoding. -In the following example, we will demonstrate the usage of both functions. We will make a call to an external REST API and enhance the result by adding products made from the fruit specified in the query parameter. For example: `http://localhost:4444/fruit?name=Strawberry` +## Example + +The following example: + +- Listens for POST requests at the /fruit endpoint on http://localhost:4444/fruit, accepting a name query parameter. +- Fetches and decodes fruit data from an external API based on the provided fruit name, then adds product recommendations. +- Returns the modified fruit data as a JSON response or an error message if the input is invalid. ```go package main import ( - "encoding/json" - "errors" - "fmt" - "io" - "log" - "net/http" - "strings" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "strings" ) type ProductsByFruit map[string][]string type Fruit struct { - Name string `json:"name"` - ID int `json:"id"` - Family string `json:"family"` - Order string `json:"order"` - Genus string `json:"genus"` - Products []string `json:"products"` + Name string `json:"name"` + ID int `json:"id"` + Family string `json:"family"` + Order string `json:"order"` + Genus string `json:"genus"` + Products []string `json:"products"` } func main() { - mux := http.NewServeMux() - mux.HandleFunc("/fruit", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - // Get query params from URL - params := r.URL.Query() - - // Validate the name of the fruit - if fruitName := params.Get("name"); len(fruitName) > 0 { - - // Set the content type for the response - w.Header().Set("Content-Type", "application/json") - - // Get the results of the invocation to the REST API - result, err := GetFruitData(fruitName) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to get fruit data: %v", err), http.StatusInternalServerError) - return - } - - // Decode the response into the fruit struct - fruit := Fruit{} - err = json.NewDecoder(result).Decode(&fruit) - if err != nil { - http.Error(w, "Error decoding data", http.StatusInternalServerError) - return - } - - // Enhance the fruit data with products - productsByFruit := ProductsByFruit{ - "strawberry": {"Smoothies", "Ice cream", "Jelly"}, - "banana": {"Banana split", "Smoothies"}, - "tomato": {"Salads", "Sauces"}, - "raspberry": {"Pies", "Smoothies"}, - "orange": {"Juice", "Jelly"}, - "blueberry": {"Smoothies", "Pies"}, - "pumpkin": {"Pies", "Latte"}, - } - - fruit.Products = productsByFruit[strings.ToLower(fruitName)] - - // Encode the modified fruit struct to the response - if err := json.NewEncoder(w).Encode(fruit); err != nil { - http.Error(w, "Error encoding data", http.StatusInternalServerError) - return - } - - } else { - http.Error(w, "Bad request, query param ?name= required", http.StatusBadRequest) - return - } - }) - - if err := http.ListenAndServe(":4444", mux); err != nil { - log.Fatal("Error occurred while starting the server: ", err) + mux := http.NewServeMux() + mux.HandleFunc("/fruit", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return } -} -func GetFruitData(name string) (io.ReadCloser, error) { - // Retrieve data from external REST API - response, err := http.Get(fmt.Sprintf("https://www.fruityvice.com/api/fruit/%s", name)) - if err != nil { - return nil, fmt.Errorf("failed to make request: %v", err) - } - if response == nil { - return nil, errors.New("no response from the server") - } + // Get query params from URL + params := r.URL.Query() + + // Validate the name of the fruit + if fruitName := params.Get("name"); len(fruitName) > 0 { - // Handle different HTTP status codes - switch response.StatusCode { - case http.StatusOK: - return response.Body, nil - case http.StatusNotFound: - return nil, errors.New("fruit not found") - default: - return nil, fmt.Errorf("server error with status: %d", response.StatusCode) + // Set the content type for the response + w.Header().Set("Content-Type", "application/json") + + // Get the results of the invocation to the REST API + result, err := GetFruitData(fruitName) + if err != nil { + http.Error(w, fmt.Sprintf("Failed to get fruit data: %v", err), http.StatusInternalServerError) + return + } + + // Decode the response into the fruit struct + fruit := Fruit{} + err = json.NewDecoder(result).Decode(&fruit) + if err != nil { + http.Error(w, "Error decoding data", http.StatusInternalServerError) + return + } + + // Enhance the fruit data with products + productsByFruit := ProductsByFruit{ + "strawberry": {"Smoothies", "Ice cream", "Jelly"}, + "banana": {"Banana split", "Smoothies"}, + "tomato": {"Salads", "Sauces"}, + "raspberry": {"Pies", "Smoothies"}, + "orange": {"Juice", "Jelly"}, + "blueberry": {"Smoothies", "Pies"}, + "pumpkin": {"Pies", "Latte"}, + } + + fruit.Products = productsByFruit[strings.ToLower(fruitName)] + if fruit.Products == nil { + fruit.Products = []string{"No specific products available"} + } + + // Encode the modified fruit struct to the response + if err := json.NewEncoder(w).Encode(fruit); err != nil { + http.Error(w, "Error encoding data", http.StatusInternalServerError) + return + } + + } else { + http.Error(w, "Bad request, query param ?name= required", http.StatusBadRequest) + return } + }) + + if err := http.ListenAndServe(":4444", mux); err != nil { + log.Fatal("Error occurred while starting the server: ", err) + } +} + +func GetFruitData(name string) (io.ReadCloser, error) { + // Retrieve data from external REST API + response, err := http.Get(fmt.Sprintf("https://www.fruityvice.com/api/fruit/%s", name)) + if err != nil { + return nil, fmt.Errorf("failed to make request: %v", err) + } + if response == nil { + return nil, errors.New("no response from the server") + } + + // Handle different HTTP status codes + switch response.StatusCode { + case http.StatusOK: + return response.Body, nil + case http.StatusNotFound: + return nil, errors.New("fruit not found") + default: + return nil, fmt.Errorf("server error with status: %d", response.StatusCode) + } } ``` +To run the code, save it in a `.go` file and execute it using the command: + +```shell +go run .go +``` + +Ensure that the Go server is running, then send a POST request with a name query parameter to `http://localhost:4444/fruit`. + ## Comparison between `json.Marshal` and `json.NewDecoder` Below is a comparison between `json.Marshal` and `json.NewDecoder` based on their use cases and performance considerations: From d829b27c5e46eb268f48e54ad54b629c49db2683 Mon Sep 17 00:00:00 2001 From: Avdhoot <50920321+avdhoottt@users.noreply.github.com> Date: Sat, 18 Jan 2025 23:26:19 +0530 Subject: [PATCH 09/10] Update content/go/concepts/working-with-json/working-with-json.md --- content/go/concepts/working-with-json/working-with-json.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/content/go/concepts/working-with-json/working-with-json.md b/content/go/concepts/working-with-json/working-with-json.md index 74e4c3cd62c..dac9a975150 100644 --- a/content/go/concepts/working-with-json/working-with-json.md +++ b/content/go/concepts/working-with-json/working-with-json.md @@ -14,9 +14,7 @@ CatalogContent: - 'paths/back-end-engineer-career-path' --- -**JSON** is one of the most commonly used data exchange formats for communication between web applications, other services, mobile applications, etc. - -Go provides built-in functions to work with JSON effectively. +**JSON** is one of the most commonly used data exchange formats for communication between web applications, other services, mobile applications, etc. Go provides built-in functions to work with JSON effectively. ## Using Marshal and Unmarshal From 7200031eaef10e9a2ee3289c744d03e5afe50686 Mon Sep 17 00:00:00 2001 From: Avdhoot <50920321+avdhoottt@users.noreply.github.com> Date: Sat, 18 Jan 2025 23:26:29 +0530 Subject: [PATCH 10/10] Update content/go/concepts/working-with-json/working-with-json.md --- content/go/concepts/working-with-json/working-with-json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/go/concepts/working-with-json/working-with-json.md b/content/go/concepts/working-with-json/working-with-json.md index dac9a975150..3c1d99da3d8 100644 --- a/content/go/concepts/working-with-json/working-with-json.md +++ b/content/go/concepts/working-with-json/working-with-json.md @@ -129,7 +129,7 @@ func main() { } ``` -When you run the above Go program, the following will happen: +When running the above Go program, the following will happen: - The menu of meals will be printed to the console. - The serialized JSON data will be printed to the console.