Go Data Structures – Chapter 2

As we have explored major Data Structures in Part I of the Fifth Chapter, let’s dive deeper into Part II –

JSON

JSON(JavaScript Object Notation) is a simple text-based data interchanging format. Basically, we use structs for JSON for putting and receiving the data. It is lightweight and gives faster performance for accessing the data

Encoding –

  • Marshal → to encode GO values to JSON in string format
  • Unmarshal → to decode JSON data to GO values
  • Encoding → same as Marshal but in streams
  • Decoding → same as unmarshaling but from streams

Go has built-in support for JSON encoding by providing functions in “json/encoding” package.

Marshalling

Marshalling converts the Golang object to JSON encoding into strings. Golang has provided marshal( ) function for this –

func Marshal( v interface { }) ([ ]byte,error)

This function will convert the given data structure into JSON in [ ]byte format which we need to convert into a string.

Example – Given data structure Person

type Person struct{
Name string
Address string
age int /* this field will not be converted into JSON as
should start with capital letter */
}

Create an instance of Person

per:=Person{“George”,”United Kingdom”,25}

Now, we can marshal encoded JSON using marshal( ) function. But don’t forget to import the “encoding/json” package.

B,err := json.Marshal(per)

If marshalling happens, then err will be nil and b will be [ ] byte containing JSON data like below.

b==[ ]byte {`{“Name”:”George”,”Address”:”United Kingdom”}`}

Here, as per the above struct age field will not come because it is not exported which is mandatory for all fields.

Now, we need to convert this [ ]byte to string. So do the typecasting

string(b)

Now, JSON will be like {“Name”:”George”,”Address”:”United Kingdom”}

Points:

  • All fields should start with a capital letter so that they will be exported
  • JSON objects only support strings as keys; if you want to encode map type to JSON then it must be in map[string] T
  • There is no way to encode of channels, and function types into JSON
  • Cyclic data structures are not supported
  • Pointers are dereferenced before encoded into JSON

Unmarshalling

Unmarshalling converts the JSON data to GO objects. Golang has provided unmarshal( ) function for this –

func Unmarshal(data [ ]byte,v interface{ })

Here, we specify JSON text as a string and convert into [ ]byte slice

Now, first, create the place of that struct type where decoded data will be stored.

var per Person

Now call json.Unmarshal( ) by passing it a []byte of JSON data and a pointer to per.

err:=json.Unmarshal(b,&per)

If the JSON fits and matches with struct fields, after the call err will be nil and data from b will have been stored in the struct m, but non-matched fields will not be decoded

String keys in JSON must be matched to struct fields.

Are You Looking For Golang Development Services?

Tags

The tags are given to the fields to attach meta information and acquired by using reflection. It is used to provide info on how struct field is encoded to or decoded from another format (or stored /retrieved from the database), but you can use it to store whatever meta-info you want either for another package or for your use.

As we see in the documentation of reflect. StructTag, by convention the value of a tag string is a space-separated key:“value” pairs.

Example –

type User struct{
Name string ` json:”name” xml:”name” ` //tag in backticks
}

If you want to send some field value empty then write “omitempty” flag in order to let JSON parser know.

type User struct{
Name string ` json:”name” xml:”name” `
Age int `json:”omitempty”`
}

With omitempty JSON value will be { }

We can use reflection(reflect package) to access the tag values of struct fields. We need to acquire the type of struct and then we can give query to fields e.g. Type.Field(i int) / Type.FieldbyName(name string)

This will return value of struct field.

The commonly used tag keys are →

  • json → used by json.encoding package, detailed at json.Marshal()
  • xml → used by encoding/xml, detailed at xml.Marshal()
  • yaml → used by gopkg.in/yaml.v2 package, at yaml.Marshal()
  • bson→ used by gobson package, detailed at bson.Marshal()
  • Other – orm, db, gorm, datastore, schema, asn
package main
import (
"encoding/json"
"fmt"
)
type person struct {
Firstname string
Lastname string
Age int `json:"Umar"` //tag in backticks
notexported int //will not be exported
}
func main() {
p1 := person{"Rob", "Pike", 24, 007}
bs1, _ := json.Marshal(p1) // Marshalling
fmt.Println(string(bs1))
bs := []byte(bs1)
json.Unmarshal(bs, p1) //Unmarshalling
fmt.Println(bs)
}

Go-Data-Structures

Encoding

Encoding works with writer and writing streams of JSON data. The GO has provided below function for encoding JSON through writer interface –

func (enc *Encoder) Encode (v interface{ }) error
json.NewEncoder(os.Stdout).Encode(p1)

If we see the source code, the NewEncoder(w *io.Writer) *Encoder takes writer and returns a pointer to an Encoder for encoding and os. Stdout is open files pointing to standard input. Stdout is a pointer to file and a pointer to a file implements func(f *file ) Write(b [ ]byte) (n int,err error) and that means it is implementing this method from Writer interface. (Polymorphism)

main.go
package main
import (
"encoding/json"
"fmt"
"os"
)
type person struct {
Name string
Age int
}
func main() {
p1 := person{"George", 23}
json.NewEncoder(os.Stdout).Encode(p1) /* Encoding through Streams */
fmt.Println(p1.Name, "--", p1.Age)
}

Go-Data-Structures

Decoding

Decoding works with reader and reading streams of JSON data. The GO has provided below function for encoding JSON through writer interface –

func (dec *Decoder) Decode (v interface{ }) error

NewReader( ) will return a reader and give it to NewDecoder( ) so that it will decode the JSON.

json.NewDecoder(reader).Decode(&p1)
main.go
package main
import (
"encoding/json"
"fmt"
"strings"
)
type person struct {
Name string
Age int
address int //this field will not be exported
}
func main() {
var p1 person
reader := strings.NewReader(`{"Name":"James", "Age":24,
"address":"New York"}`) // JSON
json.NewDecoder(reader).Decode(&p1)
fmt.Println(p1.Name)
fmt.Println(p1.Age)
fmt.Println(p1.address)
}

Go-Data-Structures

Strings

Golang’s standard library has provided built-in functions to deal with strings in the “strings” package.

Computer” → This string has 8 runes. So no need of counting for finding its length as there is built-in function len( ) for calculating its length.

In Golang, we use len( ) for a slice, array, map for measuring their length. So this makes the language more clear and understandable.

func main(){
var s string=”Computer”
fmt.Println(len(s))
}

More functions – https://golang.org/pkg/strings/

main.go
package main
import "fmt"
import s "strings" //we can also give prefixes and use
import "strings" //also we can use normally
var b = fmt.Println //prefix for fmt.Println()
func main() {
var str string = "Computer-Security"
fmt.Println(len(str)) //length: 18
fmt.Println("Contains:", s.Contains(str, "ecu")) //true
fmt.Println("Compare:", s.Compare(str, "Computer Security"))
/*return 0 if a==b,-1 if a<b,+1 if a>b*/
fmt.Println("Count", strings.Count(str, "e")) //returns 2
b("Index:", s.Index(str, "m")) // returns 2
b("ToLower", strings.ToLower(str))
b("ToUpper", strings.ToUpper(str))
b("Replace", s.Replace(str, "e", "i", 2)) /* replace e by i for 2 Occurrences */
b("Split", strings.Split(str, "-"))
}

Go-Data-Structures

Interface

An interface is an abstract type. It doesn’t expose the representation or internal structure of its values, or set of basic operation they support; it reveals only some of their methods. When you have value of an interface type, you don’t know only what it is; you only know what it can do.

Interfaces are named collection of method signatures only (like java). To implement an interface in Go, we need to implement all the methods of that interface.

Like a struct, we can create interface by specifying type keyword followed by its name and keyword interface that will contain methods.

type interface_name interface{
/* method signature (one or more method sets) */
}

Example –

type shape interface{
area( ) float64 //method
} /* interface creation with method declaration

Now anything that has same method signature implements interface like –

type square struct {
side float64
}

func (sq square) area( ) float64 { //area( ) associated with square struct
Return sq.side * sq.side
}

In above sample example, area( ) associated with square struct has same signature like shape interface area( ) that means shape interface is implemented by square struct.

See the below example,

shapeI.go
package IShape
type Shape interface {
Area() float64
}
CircleI.go
package SCircle
import ("math")
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 { //Here, Area( )is implemented
return math.Pi * c.Radius * c.Radius
}
SquareI.go
package SSquare
type Square struct {
Side float64
}
func (s Square) Area() float64 { //Here, Area( )is implemented

return s.Side * s.Side
}
main.go
package main
import (
"fmt"
"interface/basic/IShape" /* These are packages I’ve created */
"interface/basic/SCircle"
"interface/basic/SSquare"
)
func info(s IShape.Shape) { /* Now here info( ) will accept dynamically
any of type of square or circle because
Shape is implemented by both of them*/
fmt.Println(s)
fmt.Println(s.Area())

}
func main() {
s := SSquare.Square{10}
info(s) /* Here, I’ve passed square type */
c := SCircle.Circle{7}
info(c) /* Here, I’ve passed circle type */
}

This is what we call Polymorphism. Polymorphism is the ability to write code that takes on different behaviour through its implementation of types.

When you are passing an interface value, then there is no guarantee whether interface type is or isn’t pointer. In the above example, we created function based on value receiver and we passed associated values of square or circle to it.

Just take method with pointer receiver.

func (c *Circle) Area() float64 {
}

Now if we try to pass interface value to it, it will give an error

Circle does not implement Shape(Area method requires pointer receiver). So for the pointer receivers, we need to pass the *Circle pointer to the Shape instead of a Circle value, by using new(Circle) or &Circle.

Remember :

Go-Data-Structures

Interfaces are types that just declare the behavior. This behavior is never implemented by interface directly, but instead by user defined types; values of user defined type can be assigned to values & interface type. This assignment stores values of user defined type into interface value. – Bill Kenedy

Interfaces can also be used as fields.

type struct MultiShape{
shapes [ ]Shape
}

If you see “bufio” and “ioutil” package in Go’s language specification, you’ll get something like this –

func NewScanner(r io.Reader) *Scanner
→ bufio.NewScanner(res.Body)

→ In the Body interface, ReadCloser interface is implemented which again implements Reader interface that means Indirectly we are dealing with the Reader.

func ReadAll(r io.Reader) ([]byte, error)
→ ioutil.ReadAll(res.Body)

→ Same as above

func NewReader(rd io.Reader) *Reader

We need to define a meaningful name & type for fields or properties. We can initialize fields with its value or can keep blank so it will use default value.

In other languages (like Java), we have to explicitly say that this interface is implemented by other. But in Go interfaces are implemented implicitly so you don’t need to say like that you just need to use the correct method signature to use from interface.

type ReaderWriter {
Reader
Writer
} /* This is called embedding of interfaces */

A type satisfies an interface if it possesses all methods that interface requires. Conceptually, value of interface type or interface value has two components – A concrete type and concrete value.

Sort Package

  • Type Interface

Sort” package has an interface called type Interface. Sort package provides sorting of any sequence according to ordering function. Go’s Sort function assumes nothing about the representation of the sequence. Instead, it uses sort.interface for specifying sorting sequence and sorting algorithm.

An in-place algorithm needs 3 things – length of sequence for comparing two elements and way to swap two elements- so it contains 3 methods.

package sort
type Interface interface{
Len( ) int
Less(i,j int) bool
Swap(i,j int) /* Method sets */
}

So if you want to use sorting then we need to implement these methods from sort. Interface package and then apply Sort( ) on it.

Package sort provided primitives for sorting slices and user-defined collection.

main.go
package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}

type ByAge []Person /*This type of []Person has implemented sort.Interface interface*/
func (b ByAge) Len() int {
return len(b)
}
func (b ByAge) Swap(i, j int) {
b[i], b[j] = b[j], b[i]
}
func (b ByAge) Less(i, j int) bool {
return b[i].Age > b[j].Age
}
func main() {
persons := []Person{
{"George", 34},
{"Robin", 43},
{"Stuart", 23}}
peoples := []string{"Keyboard", "mouse", "Computer"}
rev := []string{"Book", "Literature", "Novel"}
ints := []int{54, 76, 33, 12, 54, 76}
fmt.Println("Before Sort:", persons)
fmt.Println("Before Sort:", peoples)
sort.Strings(peoples) /* Sorting string slice */
sort.Sort(sort.Reverse(sort.StringSlice(rev)))
fmt.Println("Reversed String Slice", rev)
sort.Sort(sort.IntSlice(ints))
fmt.Println("Sorted Integer value", ints)
fmt.Println("After Sort:", peoples)
sort.Sort(ByAge(persons))
fmt.Println("After Sort:", persons)
}

Empty Interface

Empty interface has zero methods. It is like Object class (which is superclass of all classes in java and accept any object). Similar to this, empty interface accepts any interface to itself.

An empty interface can hold any hold values of any type.

Empty interfaces are used by code that handles values of any type.

For example, fmt.Println(a …interface{ }) (n int,err error) takes any type number of arguments of type interface{ }.

main.go
package main
import (
"fmt"
)
type vehicles interface{ } //Empty interface
type vehicle struct {
speed string
color string
}
type car struct {
vehicle
wheels int
doors int
}
type plane struct {
vehicle
jet bool
}
type boat struct {
vehicle
capacity int
}
func specifications(veh interface{}) { /* accepting anything*/
fmt.Println(veh)
}
func main() {
audiR8 := car{vehicle{"240 KMPH", "White"}, 4, 4}
benz := car{vehicle{"250 KMPH", "Black"}, 4, 4}
boeing := plane{vehicle{"500 KMPH", "White"}, true}
sanger := boat{vehicle{"350 KMPH", "Saffron"}, 10000}
travelling := []vehicles{audiR8, benz, boeing, sanger}
for key, value := range travelling {
fmt.Println(key, "-", value)
}
specifications(audiR8)
specifications(benz)
specifications(boeing)
specifications(sanger)
}

Conversion vs Assertion

Conversion is converting to one type from another type like int to float. Assertion is different from conversion.

Conversion process deals with two concepts like –

  • Widening → Converting from Lower to Higher data type
  • Narrowing → Converting from Higher to Lower data type

Example –

var x=15 //int type
var y=15.45
fmt.Println(float(x)) → 15.00
fmt.Println(int(y)) → 15
var x rune=’a’
var y int32=’b’
fmt.Println(x)
fmt.Println(y)
fmt.Println(string(x)) →a
fmt.Println(string(y)) →b

Go-Data-Structures

Now come to an assertion, sometimes you want to know the exact type of interface variable, in this case, you have to use type assertion.

interface_variable.(type)
main.go
package main
import “fmt”
func display(v interface{ }){
fmt.Printf(“Value is:%v”,v.(int)) /*assert or test the correct va*/
}
func main(){
display(10)
}

O/P: 10

But here, if we put v.(string) then we will get running panic error

panic: interface conversion: interface {} is int, not string

goroutine 1 [running]:

main.display(0x497e00, 0xc0420361c0)

For this, assertion actually returns a boolean value to tell whether this operation holds or not

main.go
package main
import “fmt”
func display(v interface{ }){
if v,ok:=v.(string);ok {
fmt.Printf(“Value is:%v”,v.(int)) /*assert/test the correct val*/
}else{
fmt.Println(“Value is not string”)
}
}
func main(){
display(10)
}

O/P: Value is not string

This time o/p will be “value is not string” instead of getting a panic error. We can also use a type switch

Keep Reading

Keep Reading

  • Service
  • Career
  • Let's create something together!

  • We’re looking for the best. Are you in?