Struct
Unlike 'Slice' and 'Map', 'Struct' allows us to define our own type that holds different types of data. It is similar to a class in other object-oriented programming languages. We can store relevant data about a single concept in a single struct type.
Take the following example: person-related data such as 'name', 'age', and 'isMale' can be compiled into a single 'person' structure.
type person struct {
name string
age int
isMale bool
}
Struct type is similar to a blueprint and can be used to build struct values.
Struct values resemble instances or objects in other object-oriented programming language.
p1 := person{
name: "Jake",
age: 10,
isMale: true,
}
p2 := person{
name: "Mary",
age: 20,
isMale: false,
}
More details
1. Structs fields are fixed at compile-time.
2. The zero value of 'struct' will be a struct value with all of its fields, each of which will have the zero value of its field type.
Ex:
type person struct {
name string
age int
isMale bool
}
var p1 person
fmt.Printf("P1: %#v\n", p1)
Result:
P1: main.person{name:"", age:0, isMale:false}
3. We do not need to provide all of the field values when initializing.
Ex:
type person struct {
name string
age int
isMale bool
}
p1 := person{
name: "Jake",
isMale: true,
}
4. We can also omit the field name when initializing, and Go will automatically initialize them by matching the field ordering to the values. However, it is not recommended.
Ex:
type person struct {
name string
age int
isMale bool
}
p2 := person{
"Mary",
20,
false,
}
5. Struct, unlike Slice and Map, are bare values (similar to Array). So, when you copy one struct value to another, the changes in one will not effect the other.
Ex:
type person struct {
name string
age int
isMale bool
}
p1 := person{
name: "Jake",
age: 10,
isMale: true,
}
// Copy
p2 := p1
p2.name = "Mary"
fmt.Printf("P1: %#v\n", p1)
fmt.Printf("P2: %#v\n", p2)
Result:
P1: main.person{name:"Jake", age:10, isMale:true}
P2: main.person{name:"Mary", age:10, isMale:true}
6. If all of the struct fields are comparable, it can be compared using the equal operator.
Ex1:
type person struct {
name string
age int
isMale bool
}
p1 := person{
name: "Jake",
age: 10,
isMale: true,
}
// Copy
p2 := p1
// p2.name = "Mary"
if p1 == p2 {
fmt.Println("p1 and p2 are the same")
} else {
fmt.Println("p1 and p2 are not the same")
}
Ex2:
type person struct {
name string
age int
isMale bool
nikename []string
}
p1 := person{
name: "Jake",
age: 10,
isMale: true,
}
// Copy
p2 := p1
if p1 == p2 {
fmt.Println("p1 and p2 are the same")
} else {
fmt.Println("p1 and p2 are not the same")
}
Result:
invalid operation: p1 == p2 (struct containing []string cannot be compared)
Embedding (Object-Oriented Composition)
Before introducing 'struct embedding', let us compare inheritance and composition of OOP. Refer to this article.
Inheritance is the 'is-a' relationship.
Take the following as an example, MountainBike is-a Bicycle; RoadBike is-a Bicycle. So they are all the same type 'Bicycle'.
Ex (Java):
public class Bicycle {
// the Bicycle class has three fields
public int cadence;
public int gear;
public int speed;
}
public class MountainBike extends Bicycle {
// the MountainBike subclass adds one field
public int seatHeight;
}
public class RoadBike extends Bicycle {
// the RoadBike subclass adds one field
public string color;
}
Composition is the 'has-a' relationship.
Take the following as an example, Car has Engine; Motorcycle has Engine. So they all have their own types.
Ex (Java):
public class Engine {
public string type;
}
public class Car {
//composition has-a relationship
public Engine engine;
public bool isAWD;
}
public class Motocycle {
//composition has-a relationship
public Engine engine;
public string gloveColor;
}
Go does not use 'Inheritance'. Go use 'Composition' and call it 'Embedding'.
Ex:
type engine struct {
engineType string
}
type car struct {
// embedding field
engine engine
isAWD bool
}
type motorcycle struct {
// embedding field
engine engine
gloveColor string
}
myCar := car{
engine: engine{
engineType: "V4",
},
isAWD: true,
}
myMotorcycle := motorcycle{
engine: engine{
engineType: "V1",
},
gloveColor: "red",
}
fmt.Printf("myCar: %#v\n", myCar)
fmt.Printf("myMotorcycle: %#v\n", myMotorcycle)
// How to get the field value from the embedding field
fmt.Printf("myCar.engine: %#v\n", myCar.engine.engineType)
A struct field without a name is called an anonymous field.
When getting one field value of the anonymous composition field, we can omit the anonymous composition field name.
Ex:
type engine struct {
engineType string
}
type car struct {
// embedding field
engine engine
isAWD bool
}
type motorcycle struct {
// anonymous embedding field
engine
gloveColor string
}
myCar := car{
engine: engine{
engineType: "V4",
},
isAWD: true,
}
myMotorcycle := motorcycle{
engine: engine{
engineType: "V2",
},
gloveColor: "red",
}
fmt.Printf("myCar: %#v\n", myCar)
fmt.Printf("myMotorcycle: %#v\n", myMotorcycle)
// How to get the field value from the embedding field
fmt.Printf("myCar.engine: %#v\n", myCar.engine.engineType)
// How to get the field value from the anonymous embedding field
fmt.Printf("myMotorcycle.engine: %#v\n", myMotorcycle.engine.engineType)
fmt.Printf("myMotorcycle.engine: %#v\n", myMotorcycle.engineType)
How about the name of struct fields are the same with the one from the anonymous composition field (conflict)? Then we cannot omit the embedding field name.
Ex:
type engine struct {
engineType string
// duplicate field name
title string
}
type car struct {
// anonymous embedding field
engine engine
isAWD bool
// duplicate field name
title string
}
myCar := car{
engine: engine{
engineType: "V4",
title: "exp1",
},
isAWD: true,
title: "exp2",
}
fmt.Printf("myCar: %#v\n", myCar)
// How to get the field value from the embedding field
fmt.Printf("myCar.engine: %#v\n", myCar.engine.title)
fmt.Printf("myCar.engine: %#v\n", myCar.title)
JSON Encoding and Decoding
What is JSON?
* JSON stands for JavaScript Object Notation
* JSON is a lightweight data-interchange format
The 'encoding/json' package in Go allows us to encode (marshal) or decode (unmarshal) data.
Only struct fields beginning with an uppercase letter can be exported by the json package.
Warning:
struct field "xxx" has json tag but is not exported
Field tags are part of a struct's type. Thus, they are compile-time constants.
Field tags associate a static string metatdata to a field.
Use 'Marshal' or 'MarshalIndent' to encode data.
Ex:
type permissions map[string]bool
type user struct {
Name string `json:"username"`
Password string `json:"-"`
Permissions permissions `json:"perm,omitempty"`
}
func main() {
users := []user{
{
"Frank", "1234", nil,
},
{
"Jake", "4321", permissions{
"admin": true,
},
},
}
out, err := json.MarshalIndent(users, "", "\t")
if err != nil {
fmt.Println("err:", err)
return
}
fmt.Println(string(out))
}
Result:
[
{
"name": "Frank"
},
{
"name": "Jake",
"perm": {
"admin": true
}
}
]
Let us investigate the 'json:"perm,omitempty"' in the case above.
json:
It is the key name that allows the JSON package to read this tag.
"perm,omitempty"
Is the value of the json tag, which is encased in double quotes and separated by commas.
Use 'Unmarshal' to decode the json string to struct, slice, or map.
Ex:
type permissions map[string]bool
type user struct {
Name string `json:"username"`
Permissions permissions `json:"perm"`
}
func main() {
jsonStr := []byte{}
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
jsonStr = append(jsonStr, scanner.Bytes()...)
}
users := []user{}
json.Unmarshal(jsonStr, &users)
No comments:
Post a Comment