From 311baef9e4339b672983d2f06d846c9797a6223a Mon Sep 17 00:00:00 2001 From: Inanc Gumus Date: Tue, 3 Sep 2019 23:15:46 +0300 Subject: [PATCH] add: timestamp to reflection decoder --- interfaces/11-reflection-decoder/_decoder.go | 60 +++++++++++++++ .../11-reflection-decoder/_decoder_reader.go | 36 +++++++++ interfaces/11-reflection-decoder/_main.go | 42 ++++++++++ .../11-reflection-decoder/_main_reader.go | 30 ++++++++ interfaces/11-reflection-decoder/book.go | 38 +-------- .../11-reflection-decoder/database.json | 6 +- interfaces/11-reflection-decoder/decoder.go | 14 ++++ interfaces/11-reflection-decoder/list.go | 11 ++- interfaces/11-reflection-decoder/main.go | 18 +++++ interfaces/11-reflection-decoder/product.go | 4 +- interfaces/11-reflection-decoder/timestamp.go | 77 +++++++++++++++++++ 11 files changed, 294 insertions(+), 42 deletions(-) create mode 100644 interfaces/11-reflection-decoder/_decoder.go create mode 100644 interfaces/11-reflection-decoder/_decoder_reader.go create mode 100644 interfaces/11-reflection-decoder/_main.go create mode 100644 interfaces/11-reflection-decoder/_main_reader.go create mode 100644 interfaces/11-reflection-decoder/timestamp.go diff --git a/interfaces/11-reflection-decoder/_decoder.go b/interfaces/11-reflection-decoder/_decoder.go new file mode 100644 index 0000000..75f7669 --- /dev/null +++ b/interfaces/11-reflection-decoder/_decoder.go @@ -0,0 +1,60 @@ +// 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 ( + "encoding/json" + "reflect" +) + +type decoder map[string]interface{} + +type schema struct { + Category string + Item json.RawMessage +} + +func (d decoder) decode(data []byte) (list, error) { + var schemas []schema + + if err := json.Unmarshal(data, &schemas); err != nil { + return nil, err + } + + return d.products(schemas) +} + +func (d decoder) products(ss []schema) (list, error) { + var store list + + for _, s := range ss { + prod, err := d.product(s.Category, s.Item) + + if err != nil { + return nil, err + } + + store = append(store, prod) + } + + return store, nil +} + +func (d decoder) product(kind string, data []byte) (item, error) { + p := d.newProduct(kind) + + err := json.Unmarshal(data, p) + + return p, err +} + +func (d decoder) newProduct(kind string) item { + t := reflect.TypeOf(d[kind]) + v := reflect.New(t) + return v.Interface().(item) +} diff --git a/interfaces/11-reflection-decoder/_decoder_reader.go b/interfaces/11-reflection-decoder/_decoder_reader.go new file mode 100644 index 0000000..b9104f1 --- /dev/null +++ b/interfaces/11-reflection-decoder/_decoder_reader.go @@ -0,0 +1,36 @@ +// 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 ( + "io" + "io/ioutil" + "os" +) + +func (d decoder) fromFile(path string) (list, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + + return d.readFile(f) +} + +func (d decoder) readFile(f *os.File) (list, error) { + return d.read(f) +} + +func (d decoder) read(r io.Reader) (list, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + return d.decode(data) +} diff --git a/interfaces/11-reflection-decoder/_main.go b/interfaces/11-reflection-decoder/_main.go new file mode 100644 index 0000000..9679ed2 --- /dev/null +++ b/interfaces/11-reflection-decoder/_main.go @@ -0,0 +1,42 @@ +// 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 ( + "fmt" + "io/ioutil" + "log" + "os" +) + +func main() { + dec := make(decoder) + dec["book"] = book{} + dec["game"] = game{} + dec["puzzle"] = puzzle{} + dec["toy"] = toy{} + + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatalln(err) + } + + store, err := dec.decode(data) + if err != nil { + log.Fatalln(err) + } + + fmt.Print(store) + + // store.discount(.5) + // fmt.Print(store) + + // ---- + // data, err := save(store) + // fmt.Println(string(data), err) +} diff --git a/interfaces/11-reflection-decoder/_main_reader.go b/interfaces/11-reflection-decoder/_main_reader.go new file mode 100644 index 0000000..9dc25be --- /dev/null +++ b/interfaces/11-reflection-decoder/_main_reader.go @@ -0,0 +1,30 @@ +// 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 ( + "fmt" + "log" + "os" +) + +func main() { + dec := decoder{ + "book": book{}, + "game": game{}, + "puzzle": puzzle{}, + "toy": toy{}, + } + + store, err := dec.read(os.Stdin) + if err != nil { + log.Fatalln(err) + } + + fmt.Print(store) +} diff --git a/interfaces/11-reflection-decoder/book.go b/interfaces/11-reflection-decoder/book.go index 3e24559..c36372f 100644 --- a/interfaces/11-reflection-decoder/book.go +++ b/interfaces/11-reflection-decoder/book.go @@ -8,46 +8,14 @@ package main import ( - "encoding/json" "fmt" - "strconv" - "time" ) type book struct { product - Published interface{} + Published timestamp } -func (b book) String() string { - p := format(b.Published) - return fmt.Sprintf("%s - (%v)", &b.product, p) -} - -func (b *book) MarshalJSON() ([]byte, error) { - type jbook book - - jb := (*jbook)(b) - jb.Published = format(b.Published) - - return json.Marshal(jb) -} - -func format(v interface{}) string { - var t int - - switch v := v.(type) { - case int: - t = v - case string: - t, _ = strconv.Atoi(v) - default: - return "unknown" - } - - // Mon Jan 2 15:04:05 -0700 MST 2006 - const layout = "2006/01" - - u := time.Unix(int64(t), 0) - return u.Format(layout) +func (b *book) String() string { + return fmt.Sprintf("%s - (%s)", &b.product, b.Published) } diff --git a/interfaces/11-reflection-decoder/database.json b/interfaces/11-reflection-decoder/database.json index 92e780c..5caa227 100644 --- a/interfaces/11-reflection-decoder/database.json +++ b/interfaces/11-reflection-decoder/database.json @@ -4,7 +4,7 @@ "Item": { "Title": "moby dick", "Price": 10, - "Published": "1973/10" + "Published": 118281600 } }, { @@ -12,7 +12,7 @@ "Item": { "Title": "odyssey", "Price": 15, - "Published": "1993/04" + "Published": 733622400 } }, { @@ -20,7 +20,7 @@ "Item": { "Title": "hobbit", "Price": 25, - "Published": "unknown" + "Published": -62135596800 } }, { diff --git a/interfaces/11-reflection-decoder/decoder.go b/interfaces/11-reflection-decoder/decoder.go index c1ea908..feb7fe4 100644 --- a/interfaces/11-reflection-decoder/decoder.go +++ b/interfaces/11-reflection-decoder/decoder.go @@ -61,3 +61,17 @@ func newProduct(category string) item { } return nil } + +// func (d decoder) newProduct(category string) item { +// t := reflect.TypeOf(d[category]) +// v := reflect.New(t) +// return v.Interface().(item) +// } + +// func toProduct(kind string, data []byte) (item, error) { +// p := newProduct(kind) + +// err := json.Unmarshal(data, p) + +// return p, err +// } diff --git a/interfaces/11-reflection-decoder/list.go b/interfaces/11-reflection-decoder/list.go index 0a7dede..3d644a3 100644 --- a/interfaces/11-reflection-decoder/list.go +++ b/interfaces/11-reflection-decoder/list.go @@ -19,9 +19,15 @@ type item interface { fmt.Stringer } +// schema adds an additional category field +// to the product types. type schema struct { + // Category is the product category field. Category string - Item item + + // Item is an interface value. + // Here, we are embedding an interface value. + Item item } type list []item @@ -30,6 +36,9 @@ func (l list) MarshalJSON() ([]byte, error) { var schemas []schema for _, it := range l { + // TypeOf -> finds the dynamic type of "it" + // Elem -> returns the element type of the pointer + // Name -> returns the type name as string cat := reflect.TypeOf(it).Elem().Name() schemas = append(schemas, schema{cat, it}) } diff --git a/interfaces/11-reflection-decoder/main.go b/interfaces/11-reflection-decoder/main.go index 30b76f4..fefd0a1 100644 --- a/interfaces/11-reflection-decoder/main.go +++ b/interfaces/11-reflection-decoder/main.go @@ -14,6 +14,23 @@ import ( ) func main() { + // store := list{ + // &book{product{"moby dick", 10}, toTimestamp(118281600)}, + // &book{product{"odyssey", 15}, toTimestamp("733622400")}, + // &book{product{"hobbit", 25}, unknown}, + // &puzzle{product{"rubik's cube", 5}}, + // &game{product{"minecraft", 20}}, + // &game{product{"tetris", 5}}, + // &toy{product{"yoda", 150}}, + // } + + // out, err := json.MarshalIndent(store, "", "\t") + // if err != nil { + // log.Fatalln(err) + // } + + // fmt.Println(string(out)) + data, err := ioutil.ReadFile("database.json") if err != nil { log.Fatalln(err) @@ -25,6 +42,7 @@ func main() { } fmt.Print(store) + // for _, p := range store { // fmt.Printf("%#v\n", p) // } diff --git a/interfaces/11-reflection-decoder/product.go b/interfaces/11-reflection-decoder/product.go index 2fd584d..f93cb84 100644 --- a/interfaces/11-reflection-decoder/product.go +++ b/interfaces/11-reflection-decoder/product.go @@ -7,9 +7,7 @@ package main -import ( - "fmt" -) +import "fmt" type product struct { Title string diff --git a/interfaces/11-reflection-decoder/timestamp.go b/interfaces/11-reflection-decoder/timestamp.go new file mode 100644 index 0000000..dad84a2 --- /dev/null +++ b/interfaces/11-reflection-decoder/timestamp.go @@ -0,0 +1,77 @@ +// 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 ( + "strconv" + "time" +) + +// Mon Jan 2 15:04:05 -0700 MST 2006 +const layout = "2006/01" + +// unknown is the zero value of a timestamp. +var unknown = timestamp(time.Time{}) + +// timestamp prints timestamps, it's a stringer. +// It is useful even if it's zero. +type timestamp time.Time + +// MarshalJSON is an implementation of the json.Marshaler interface. +// json.Marshal and json.Encode call this method. +func (ts timestamp) MarshalJSON() (out []byte, err error) { + u := time.Time(ts).Unix() + return strconv.AppendInt(out, u, 10), nil +} + +// UnmarshalJSON is an implementation of the json.Unmarshaler interface. +// json.Unmarshal and json.Decode call this method. +func (ts *timestamp) UnmarshalJSON(data []byte) error { + s := string(data) + + // Let the ParseInt parse quoted strings. + us, err := strconv.Unquote(s) + if err == nil { + s = us + } + + // Always overwrite the timestamp when decoding. + *ts = unknown + + // Handle the numeric case. + if n, err := strconv.ParseInt(s, 10, 64); err == nil { + *ts = timestamp(time.Unix(n, 0)) + } + + return nil +} + +func (ts timestamp) String() string { + t := time.Time(ts) + + if t.IsZero() { + return "unknown" + } + + return t.Format(layout) +} + +func toTimestamp(v interface{}) timestamp { + var t int + + switch v := v.(type) { + case int: + t = v + case string: + t, _ = strconv.Atoi(v) + default: + return unknown + } + + return timestamp(time.Unix(int64(t), 0)) +}