golang USES json format to implement the implementation of the sample
- 2020-10-23 21:00:08
- OfStack
Needs and ideas
Persistence of data may be required in a small project or a small piece of software, such as a client. But using something like a 1-like database (Mysql) is not appropriate. An embedded approach like sqlite3 is a good one, but the sqlite3 library in Go is in C, and Cgo does not support cross-platform compilation. It was this need that led to the idea of using the json format to store the data directly in a file.
What's the specific idea? In Go, if you want to convert data to json format, there are two formats, struct and map. If you need to add, delete, check and change functions at the same time, map as the intermediate format is more appropriate. So let's implement that.
Query operation
The implementation of this operation is relatively simple, directly read the data from the file, using the json library deserialization can be. The code is as follows:
type Product struct {
Name string `json:"name"`
Num int `json:"num"`
}
func findAll() {
ps := make([]Product, 0)
data, err := ioutil.ReadFile("./index.json")
if err != nil {
log.Fatal(err)
}
// The parameter is specified as the address of the variable
err = json.Unmarshal(data, &ps)
if err != nil {
log.Fatal(err)
}
fmt.Println(ps)
}
Add operation
The added implementation is based on actual query, we need to query the database in the file first, and convert it to map format, then convert struct to map format (using reflection here), merge map,json serialization, and finally save in the file. The code is as follows:
func create() {
fields := make([]map[string]interface{}, 0)
p1 := &Product{
Name: "Blog",
Num: 2,
}
_, _ = json.Marshal(p1)
// Read the data in the file , Save as map format
data, _ := ioutil.ReadFile("./index.json")
err := json.Unmarshal(data, &fields)
if err != nil {
log.Fatal(err)
}
// Using reflection will struct into map
tp := reflect.TypeOf(p1).Elem()
vp := reflect.ValueOf(p1).Elem()
field := make(map[string]interface{}, 0)
for i := 0; i < tp.NumField(); i++ {
field1 := tp.Field(i)
field2 := vp.Field(i)
key := field1.Tag.Get("json")
field[key] = field2.Interface()
}
// merge map
fields = append(fields, field)
// Written to the file
out, _ := json.Marshal(fields)
_ = ioutil.WriteFile("./index.json", out, 0755)
}
Conditions of the query
Idea: Convert struct to map and query according to the input condition. The result of the query is converted to struct. the code is as follows:
func FindOne() {
product := &Product{}
p1 := &Product{
Name: "John",
Num: 23,
}
// Using reflection will struct into map
tp := reflect.TypeOf(p1).Elem()
vp := reflect.ValueOf(p1).Elem()
field := make(map[string]interface{}, 0)
for i := 0; i < tp.NumField(); i++ {
field1 := tp.Field(i)
field2 := vp.Field(i)
key := field1.Tag.Get("json")
switch field2.Kind() {
case reflect.Int:
field[key] = float64(field2.Interface().(int))
case reflect.Int8:
field[key] = float64(field2.Interface().(int8))
case reflect.Int16:
field[key] = float64(field2.Interface().(int16))
case reflect.Int32:
field[key] = float64(field2.Interface().(int32))
case reflect.Int64:
field[key] = float64(field2.Interface().(int64))
case reflect.Uint:
field[key] = float64(field2.Interface().(uint))
case reflect.Uint8:
field[key] = float64(field2.Interface().(uint8))
case reflect.Uint16:
field[key] = float64(field2.Interface().(uint16))
case reflect.Uint32:
field[key] = float64(field2.Interface().(uint32))
case reflect.Uint64:
field[key] = float64(field2.Interface().(uint64))
case reflect.Float32:
field[key] = float64(field2.Interface().(float32))
case reflect.Float64:
field[key] = field2.Interface()
default:
field[key] = field2.Interface()
}
}
_, _ = json.Marshal(p1)
// Read the data in the file , Save as map format
// Convert data into map when , A series of numerical types 1 become float64
data, _ := ioutil.ReadFile("./index.json")
fields := make([]map[string]interface{}, 0)
err := json.Unmarshal(data, &fields)
if err != nil {
log.Fatal(err)
}
// Condition of the query
columns := []string{"name", "num"}
length := len(columns)
for _, item := range fields {
for i := 0; i < length; i++ {
// The comparison here needs to be improved
if item[columns[i]] != field[columns[i]] {
break
}
if i == length-1 {
field = item
goto OVER
}
}
}
OVER:
fmt.Println(field)
out, _ := json.Marshal(field)
_ = json.Unmarshal(out, &product)
fmt.Println(product)
}
Modify the operating
The modification operation is implemented on the basis of the query operation. The modification operation needs to have a value of id, which can determine the uniqueness of the element. The code is as follows:
func Update() {
p1 := &Product{
Id: "2bbec87025968879c3c9682abe3bf730",
Name: "John_e",
Num: 100,
}
// Using reflection will struct into map
tp := reflect.TypeOf(p1).Elem()
vp := reflect.ValueOf(p1).Elem()
field := make(map[string]interface{}, 0)
for i := 0; i < tp.NumField(); i++ {
field1 := tp.Field(i)
field2 := vp.Field(i)
key := field1.Tag.Get("json")
switch field2.Kind() {
case reflect.Int:
field[key] = float64(field2.Interface().(int))
case reflect.Int8:
field[key] = float64(field2.Interface().(int8))
case reflect.Int16:
field[key] = float64(field2.Interface().(int16))
case reflect.Int32:
field[key] = float64(field2.Interface().(int32))
case reflect.Int64:
field[key] = float64(field2.Interface().(int64))
case reflect.Uint:
field[key] = float64(field2.Interface().(uint))
case reflect.Uint8:
field[key] = float64(field2.Interface().(uint8))
case reflect.Uint16:
field[key] = float64(field2.Interface().(uint16))
case reflect.Uint32:
field[key] = float64(field2.Interface().(uint32))
case reflect.Uint64:
field[key] = float64(field2.Interface().(uint64))
case reflect.Float32:
field[key] = float64(field2.Interface().(float32))
case reflect.Float64:
field[key] = field2.Interface()
default:
field[key] = field2.Interface()
}
}
_, _ = json.Marshal(p1)
// Read the data in the file , Save as map format
// Convert data into map when , A series of numerical types 1 become float64
data, _ := ioutil.ReadFile("./index.json")
fields := make([]map[string]interface{}, 0)
err := json.Unmarshal(data, &fields)
if err != nil {
log.Fatal(err)
}
// Modified condition
columns := []string{"name", "num"}
for _, v := range fields {
if v["_id"] == field["_id"] {
for _, col := range columns {
v[col] = field[col]
}
field = v
}
}
out, _ := json.MarshalIndent(fields, "", " ")
_ = ioutil.WriteFile("./index.json", out, 0755)
}
Delete operation
Finally, the delete operation, which is relatively simple, enter the value of only 1 id, delete the corresponding field, and then save to the file. The code is as follows:
func Delete() {
p1 := &Product{
Id: "db43fa2d4f69cddce7494941cb36032b",
Name: "John_e",
Num: 100,
}
_, _ = json.Marshal(p1)
// Read the data in the file , Save as map format
// Convert data into map when , A series of numerical types 1 become float64
data, _ := ioutil.ReadFile("./index.json")
fields := make([]map[string]interface{}, 0)
err := json.Unmarshal(data, &fields)
if err != nil {
log.Fatal(err)
}
length := len(fields)
for index, field := range fields {
if field["_id"] == p1.Id {
if index == length - 1 {
fields = fields[0:index]
} else {
fields = append(fields[0:index], fields[index+1:]...)
}
}
}
out, _ := json.MarshalIndent(fields, "", " ")
_ = ioutil.WriteFile("./index.json", out, 0755)
}
The full version
Finally, attach the complete code:
package store
import (
"bytes"
"crypto/md5"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"time"
)
type Store struct {
Dir string
}
func NewStore(dir string) (*Store, error) {
// . The first one is a relative path , Completion is the full path
if strings.HasPrefix(dir, ".") {
pwd, _ := os.Getwd()
dir = filepath.Join(pwd, dir)
}
store := &Store{Dir: dir}
st, err := os.Stat(dir)
if err != nil {
err = os.Mkdir(dir, 0755)
if err != nil {
return nil, err
}
} else if st != nil && !st.IsDir() {
return nil, errors.New("file already exists")
}
return store, nil
}
// Creates the corresponding structure json file
func (s *Store) Sync(values ...interface{}) error {
for _, v := range values {
tb := parseTn(v)
if tb == "" {
return errors.New("does not find store")
}
_path := filepath.Join(s.Dir, tb)
_, err := os.Stat(_path)
if err != nil {
_ = ioutil.WriteFile(_path, []byte("[]"), 0755)
}
}
return nil
}
// Delete all
func (s *Store) Destroy() error {
return os.RemoveAll(s.Dir)
}
func (s *Store) FindAll(v interface{}) error {
_path, err := s.before(v)
if err != nil {
return err
}
out, err := s.readAll(_path)
if err != nil {
return err
}
err = json.Unmarshal(out, &v)
return err
}
func (s *Store) FindOne(v interface{}, columns ...string) (interface{}, error) {
_path, err := s.before(v)
if err != nil {
return nil, err
}
data, err := s.readAll(_path)
if err != nil {
return nil, err
}
fields := make([]map[string]interface{}, 0)
err = json.Unmarshal(data, &fields)
if err != nil {
return nil, err
}
m := structToMap(v)
length := len(columns)
for _, item := range fields {
for i := 0; i < length; i++ {
// TODO The comparison here needs to be improved
if item[columns[i]] != m[columns[i]] {
break
}
if i == length-1 {
m = item
goto OVER
}
}
}
OVER:
err = mapToStruct(m, &v)
if err != nil {
return nil, err
}
return v, nil
}
func (s *Store) Create(v interface{}) error {
_path, err := s.before(v)
if err != nil {
return err
}
data, err := s.readAll(_path)
if err != nil {
return err
}
fields := make([]map[string]interface{}, 0)
err = json.Unmarshal(data, &fields)
if err != nil {
return err
}
m := structToMap(v)
m["_id"] = randId()
fields = append(fields, m)
err = s.writeAll(_path, fields)
if err != nil {
return err
}
err = mapToStruct(m, v)
if err != nil {
return err
}
return nil
}
func (s *Store) Update(v interface{}, columns ...string) error {
_path, err := s.before(v)
if err != nil {
return err
}
data, err := s.readAll(_path)
if err != nil {
return err
}
fields := make([]map[string]interface{}, 0)
err = json.Unmarshal(data, &fields)
if err != nil {
return err
}
m := structToMap(v)
for _, v := range fields {
if v["_id"] == m["_id"] {
for _, col := range columns {
v[col] = m[col]
}
m = v
}
}
err = s.writeAll(_path, fields)
if err != nil {
return err
}
return nil
}
func (s *Store) Delete(v interface{}) error {
_path, err := s.before(v)
if err != nil {
return err
}
data, err := s.readAll(_path)
if err != nil {
return err
}
fields := make([]map[string]interface{}, 0)
err = json.Unmarshal(data, &fields)
if err != nil {
return err
}
m := structToMap(v)
length := len(fields)
for index, field := range fields {
if field["_id"] == m["_id"] {
if index == length-1 {
fields = fields[0:index]
} else {
fields = append(fields[0:index], fields[index+1:]...)
}
}
}
err = s.writeAll(_path, fields)
if err != nil {
return err
}
return nil
}
func (s *Store) Clean(v interface{}) error {
_path, err := s.before(v)
if err != nil {
return err
}
return os.Remove(_path)
}
func (s *Store) readAll(file string) ([]byte, error) {
out, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
return out, nil
}
func (s *Store) writeAll(file string, v interface{}) error {
out, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
err = ioutil.WriteFile(file, out, 0755)
if err != nil {
return err
}
return nil
}
func (s *Store) before(v interface{}) (string, error) {
tb := parseTn(v)
if tb == "" {
return "", errors.New("invalid table name")
}
_path := filepath.Join(s.Dir, tb)
_, err := os.Stat(_path)
if err != nil {
return "", err
}
return _path, nil
}
func structToMap(v interface{}) map[string]interface{} {
tp := reflect.TypeOf(v).Elem()
vp := reflect.ValueOf(v).Elem()
field := make(map[string]interface{}, 0)
for i := 0; i < tp.NumField(); i++ {
field1 := tp.Field(i)
field2 := vp.Field(i)
key := field1.Tag.Get("json")
field[key] = field2.Interface()
switch field2.Kind() {
case reflect.Int:
field[key] = float64(field2.Interface().(int))
case reflect.Int8:
field[key] = float64(field2.Interface().(int8))
case reflect.Int16:
field[key] = float64(field2.Interface().(int16))
case reflect.Int32:
field[key] = float64(field2.Interface().(int32))
case reflect.Int64:
field[key] = float64(field2.Interface().(int64))
case reflect.Uint:
field[key] = float64(field2.Interface().(uint))
case reflect.Uint8:
field[key] = float64(field2.Interface().(uint8))
case reflect.Uint16:
field[key] = float64(field2.Interface().(uint16))
case reflect.Uint32:
field[key] = float64(field2.Interface().(uint32))
case reflect.Uint64:
field[key] = float64(field2.Interface().(uint64))
case reflect.Float32:
field[key] = float64(field2.Interface().(float32))
case reflect.Float64:
field[key] = field2.Interface()
default:
field[key] = field2.Interface()
}
}
return field
}
func mapToStruct(m map[string]interface{}, v interface{}) error {
out, err := json.Marshal(m)
if err != nil {
return err
}
return json.Unmarshal(out, &v)
}
func toSnake(s string) string {
out := bytes.Buffer{}
bName := []byte(s)
point := 0
for index, b := range bName {
// The capital , You don't have to convert
if b < 65 || b > 90 || index-point < 2 {
out.WriteByte(b)
continue
}
// Uppercase , I'm going to go straight to lowercase
if index == 0 {
out.WriteByte(b + 32)
point = index
}
// continuous 3 A capital , Triggers the transformation
if index-point >= 2 {
out.WriteByte(95)
out.WriteByte(b + 32)
point = index
}
}
return out.String()
}
func parseTn(v interface{}) string {
var name string
tp := reflect.TypeOf(v).Elem()
switch tp.Kind() {
case reflect.Ptr:
sp := strings.Split(tp.String(), ".")
name = sp[len(sp)-1]
case reflect.Slice:
sp := strings.Split(tp.String(), ".")
name = sp[len(sp)-1]
case reflect.Struct:
name = tp.Name()
}
name = toSnake(name)
return name + ".json"
}
func randId() string {
return fmt.Sprintf("%x", md5.Sum([]byte(time.Now().String())))
}