commit 34bc26cb4839f43f49cc932be686c4e037a13926 Author: what-00 Date: Sat Apr 15 00:06:52 2023 +0800 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..8b79f66 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +#go-jsonpack diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3b47b9f --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module git.fsdpf.net/go/jsonpack + +go 1.18 + +require github.com/samber/lo v1.38.1 + +require golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7a26b7e --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= diff --git a/jsonpack.go b/jsonpack.go new file mode 100644 index 0000000..d11677c --- /dev/null +++ b/jsonpack.go @@ -0,0 +1,43 @@ +package jsonpack + +import ( + "errors" + "unicode/utf8" +) + +const ( + TOKEN_TRUE = -(iota + 1) + TOKEN_FALSE + TOKEN_NULL + TOKEN_EMPTY_STRING +) + +// 将对象加密为字符串 +func Pack(json any) (string, error) { + pack := &JsonEncode{} + + ast, err := pack.astBuilder(json) + + if err != nil { + return "", err + } + + return pack.toString(ast) +} + +// 解密数据到JSON字符串 +func Unpack(json string) (string, error) { + if !utf8.ValidString(json) { + return "", errors.New("jsonpack 无效的字符串格式, UTF8") + } + + unpack := &JsonDecode{} + + ast, err := unpack.astBuilder(json) + + if err != nil { + return "", err + } + + return unpack.toString(ast) +} diff --git a/jsonpackAst.go b/jsonpackAst.go new file mode 100644 index 0000000..8274e51 --- /dev/null +++ b/jsonpackAst.go @@ -0,0 +1,110 @@ +package jsonpack + +import ( + "math" + "regexp" + "strings" + + "github.com/samber/lo" +) + +type AstToken struct { + category string // array null, + index int + child []AstToken +} + +var baseCharacters = []rune("0123456789abcdefghijklmnopqrstuvwxyz") + +var baseCharactersMap = lo.Reduce[rune, map[rune]int](baseCharacters, func(carry map[rune]int, v rune, i int) map[rune]int { + carry[v] = i + return carry +}, make(map[rune]int)) + +var astEncodeReplace = map[string]string{ + " ": "+", + "+": "%2B", + "|": "%7C", + "^": "%5E", + "%": "%25", +} + +var astDecodeReplace = map[string]string{ + "+": " ", + "%2B": "+", + "%7C": "|", + "%5E": "^", + "%25": "%", +} + +// 字符串编码 +func astEncode(str string) string { + reg := regexp.MustCompile(`[\+ \|\^\%]`) + + return reg.ReplaceAllStringFunc(str, func(s string) string { + return astEncodeReplace[s] + }) +} + +// 字符串界面 +func astDecode(str string) string { + reg := regexp.MustCompile(`\+|%2B|%7C|%5E|%25`) + + return reg.ReplaceAllStringFunc(str, func(s string) string { + return astDecodeReplace[s] + }) +} + +// 10进制转36进制 +func astBase10to36(num int) string { + if num == 0 { + return "0" + } + + isSubFlag := num < 0 + + if isSubFlag { + num = num * -1 + } + + str := "" + + for num != 0 { + p := num % 36 + str = string(baseCharacters[p]) + str + num = num / 36 + } + + if isSubFlag { + return "-" + str + } + + return str +} + +// 36进制转10进制 +func astBase36to10(str string) int { + str = strings.ToLower(str) + + isSubFlag := str[0:1] == "-" + + if isSubFlag { + str = str[1:] + } + + v := 0.0 + + length := len(str) + + for i := 0; i < length; i++ { + s := rune(str[i]) + index := baseCharactersMap[s] + v += float64(index) * math.Pow(float64(36), float64(length-1-i)) // 倒序 + } + + if isSubFlag { + return -int(v) + } + + return int(v) +} diff --git a/jsonpackDecode.go b/jsonpackDecode.go new file mode 100644 index 0000000..7d38de6 --- /dev/null +++ b/jsonpackDecode.go @@ -0,0 +1,272 @@ +package jsonpack + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +type JsonDecode struct { + dictionaries []any + tokenIndex int +} + +func (oJsonDecode *JsonDecode) astBuilder(packed string) (string, error) { + aStr := strings.Split(packed, "^") + + if len(aStr) != 4 { + return "", errors.New("jsonpack 解析错误") + } + + if aStr[0] != "" { + for _, v := range strings.Split(aStr[0], "|") { + oJsonDecode.dictionaries = append(oJsonDecode.dictionaries, astDecode(v)) + } + } + + if aStr[1] != "" { + for _, v := range strings.Split(aStr[1], "|") { + oJsonDecode.dictionaries = append(oJsonDecode.dictionaries, astBase36to10(v)) + } + } + + if aStr[2] != "" { + for _, v := range strings.Split(aStr[2], "|") { + if value, err := strconv.ParseFloat(v, 64); err == nil { + oJsonDecode.dictionaries = append(oJsonDecode.dictionaries, value) + } else { + return "", fmt.Errorf("jsonpack 浮点数转换失败 %#v", v) + } + } + } + + return aStr[3], nil +} + +func (oJsonDecode *JsonDecode) astParser(tokens *[]rune) (string, error) { + len := len(*tokens) + + symbol := (*tokens)[oJsonDecode.tokenIndex] + + oJsonDecode.tokenIndex = oJsonDecode.tokenIndex + 1 + + if symbol == '@' { + node := []string{} + + for ; oJsonDecode.tokenIndex < len; oJsonDecode.tokenIndex++ { + value := rune((*tokens)[oJsonDecode.tokenIndex]) + + if value == ']' { + return "[" + strings.Join(node, ",") + "]", nil + } + + if value == '@' || value == '$' { + // 递归解析 + if res, err := oJsonDecode.astParser(tokens); err == nil { + node = append(node, res) + } else { + return "", errors.New("jsonpack 递归解析错误") + } + } else { + // 解析成员 + if res, err := oJsonDecode.astParserValue(value); err == nil { + node = append(node, res) + } else { + return "", errors.New("jsonpack 解析成员错误") + } + } + } + + return "[" + strings.Join(node, ",") + "]", nil + } + + if symbol == '$' { + node := []string{} + for ; oJsonDecode.tokenIndex < len; oJsonDecode.tokenIndex++ { + var key string + + kIndex := rune((*tokens)[oJsonDecode.tokenIndex]) + + if kIndex == ']' { + return "{" + strings.Join(node, ",") + "}", nil + } + + if kIndex == TOKEN_EMPTY_STRING { + key = "" + } else { + key = strconv.Quote(oJsonDecode.dictionaries[kIndex].(string)) + } + + oJsonDecode.tokenIndex++ + + value := rune((*tokens)[oJsonDecode.tokenIndex]) + + if value == '@' || value == '$' { + // 递归解析 + if res, err := oJsonDecode.astParser(tokens); err == nil { + node = append(node, key+":"+res) + } else { + return "", err + } + } else { + // 解析成员 + if res, err := oJsonDecode.astParserValue(value); err == nil { + node = append(node, key+":"+res) + } else { + return "", err + } + } + } + + return "{" + strings.Join(node, ",") + "}", nil + } + + return "", errors.New("jsonpack 解析错误, 未知符号") +} + +func (oJsonDecode *JsonDecode) astParserValue(value rune) (string, error) { + switch value { + case TOKEN_TRUE: + return "true", nil + case TOKEN_FALSE: + return "false", nil + case TOKEN_NULL: + return "null", nil + case TOKEN_EMPTY_STRING: + return `""`, nil + default: + item := oJsonDecode.dictionaries[value] + + switch reflect.ValueOf(item).Kind() { + case reflect.String: + return strconv.Quote(item.(string)), nil + case reflect.Int, reflect.Float64: + return fmt.Sprintf("%v", item), nil + } + } + + return "", errors.New("jsonpack 未知数据类型") +} + +func (oJsonDecode *JsonDecode) toString(token string) (string, error) { + var number36 strings.Builder + + var tokens []rune + + len := len(token) + + for i := 0; i < len; i++ { + symbol := rune(token[i]) + + if symbol == '|' || symbol == '$' || symbol == '@' || symbol == ']' { + if number36.Len() > 0 { + tokens = append(tokens, rune(astBase36to10(number36.String()))) + + number36.Reset() + } + + if symbol != '|' { + tokens = append(tokens, symbol) + } + + } else { + number36.WriteRune(symbol) + } + } + + return oJsonDecode.astParser(&tokens) +} + +// func (oJsonDecode *JsonDecode) astParser(tokens *[]rune) (any, error) { +// len := len(*tokens) + +// symbol := (*tokens)[oJsonDecode.tokenIndex] + +// oJsonDecode.tokenIndex = oJsonDecode.tokenIndex + 1 + +// if symbol == '@' { +// node := []any{} + +// for ; oJsonDecode.tokenIndex < len; oJsonDecode.tokenIndex++ { +// value := rune((*tokens)[oJsonDecode.tokenIndex]) + +// if value == ']' { +// return node, nil +// } + +// if value == '@' || value == '$' { +// if resp, err := oJsonDecode.astParser(tokens); err == nil { +// node = append(node, resp) +// } else { +// return nil, errors.New("jsonpack 解析失败") +// } +// } else { +// switch value { +// case TOKEN_TRUE: +// node = append(node, true) +// case TOKEN_FALSE: +// node = append(node, false) +// case TOKEN_NULL: +// node = append(node, nil) +// case TOKEN_EMPTY_STRING: +// node = append(node, "") +// default: +// node = append(node, oJsonDecode.dictionaries[value]) +// } +// } +// } + +// return node, nil +// } + +// if symbol == '$' { +// node := map[string]any{} + +// for ; oJsonDecode.tokenIndex < len; oJsonDecode.tokenIndex++ { +// var key string + +// kIndex := rune((*tokens)[oJsonDecode.tokenIndex]) + +// if kIndex == ']' { +// return node, nil +// } + +// if kIndex == TOKEN_EMPTY_STRING { +// key = "" +// } else { +// key = oJsonDecode.dictionaries[kIndex].(string) +// } + +// oJsonDecode.tokenIndex++ + +// value := rune((*tokens)[oJsonDecode.tokenIndex]) + +// if value == '@' || value == '$' { +// if res, err := oJsonDecode.astParser(tokens); err == nil { +// node[key] = res +// } else { +// return nil, errors.New("jsonpack 递归解析 数组或对象 错误") +// } +// } else { +// switch value { +// case TOKEN_TRUE: +// node[key] = true +// case TOKEN_FALSE: +// node[key] = false +// case TOKEN_NULL: +// node[key] = nil +// case TOKEN_EMPTY_STRING: +// node[key] = "" +// default: +// node[key] = oJsonDecode.dictionaries[value] +// } +// } +// } + +// return node, nil +// } + +// return nil, errors.New("jsonpack 解析错误, 未知符号") +// } diff --git a/jsonpackEncode.go b/jsonpackEncode.go new file mode 100644 index 0000000..afd9c73 --- /dev/null +++ b/jsonpackEncode.go @@ -0,0 +1,185 @@ +package jsonpack + +import ( + "errors" + "fmt" + "reflect" + "strings" + + "github.com/samber/lo" +) + +type JsonEncode struct { + strings []string + integers []string + floats []string +} + +func (oJsonEncode *JsonEncode) astBuilder(item any) (AstToken, error) { + itemType := reflect.ValueOf(item).Kind() + + if item == nil { + return AstToken{category: "null", index: TOKEN_NULL}, nil + } + + if itemType == reflect.Slice { + tokens := []AstToken{} + + value := reflect.ValueOf(item) + + for i := 0; i < value.Len(); i++ { + // value.Index(i).Interface() 把 value 转换为 interface{} 类型 + if resp, err := oJsonEncode.astBuilder(value.Index(i).Interface()); err == nil { + tokens = append(tokens, resp) + } else { + return AstToken{}, err + } + } + + // return tokens, nil + + // Create a new sub-AST of type Array (@) + return AstToken{category: "@", child: tokens}, nil + } + + if itemType == reflect.Map { + // Create a new sub-AST of type Object ($) + // tokens := []any{AstToken{category: "$"}} + tokens := []AstToken{} + + iter := reflect.ValueOf(item).MapRange() + + for iter.Next() { + if resp, err := oJsonEncode.astBuilder(iter.Key().Interface()); err == nil { + tokens = append(tokens, resp) + } else { + return AstToken{}, err + } + + if resp, err := oJsonEncode.astBuilder(iter.Value().Interface()); err == nil { + tokens = append(tokens, resp) + } else { + return AstToken{}, err + } + } + + // return tokens, nil + return AstToken{category: "$", child: tokens}, nil + } + + if item == "" { + return AstToken{category: "empty", index: TOKEN_EMPTY_STRING}, nil + } + + if itemType == reflect.String { + value := item.(string) + index := lo.IndexOf[string](oJsonEncode.strings, value) + + if index == -1 { + oJsonEncode.strings = append(oJsonEncode.strings, astEncode(value)) + index = len(oJsonEncode.strings) - 1 + } + + return AstToken{category: "strings", index: index}, nil + } + + switch itemType { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + + value := astBase10to36(item.(int)) + index := lo.IndexOf[string](oJsonEncode.integers, value) + + if index == -1 { + oJsonEncode.integers = append(oJsonEncode.integers, value) + index = len(oJsonEncode.integers) - 1 + } + + return AstToken{category: "integers", index: index}, nil + } + + if itemType == reflect.Float32 || itemType == reflect.Float64 { + value := fmt.Sprintf("%v", item.(float64)) + index := lo.IndexOf[string](oJsonEncode.floats, value) + + if index == -1 { + // 浮点数不用转36进制 + oJsonEncode.floats = append(oJsonEncode.floats, value) + index = len(oJsonEncode.floats) - 1 + } + + return AstToken{category: "floats", index: index}, nil + } + + if itemType == reflect.Bool { + if item.(bool) { + return AstToken{category: "boolean", index: TOKEN_TRUE}, nil + } else { + return AstToken{category: "boolean", index: TOKEN_FALSE}, nil + } + } + + return AstToken{}, errors.New(fmt.Sprintln("未知类型:", itemType, item)) +} + +func (oJsonEncode *JsonEncode) astParser(token AstToken) (string, error) { + + if token.category == "@" || token.category == "$" { + var packed strings.Builder + + packed.WriteString(token.category) + + childPacked := []string{} + + for _, tk := range token.child { + if resp, err := oJsonEncode.astParser(tk); err == nil { + childPacked = append(childPacked, resp) + } else { + return "", err + } + } + + packed.WriteString(strings.Join(childPacked, "|")) + packed.WriteString("]") + + return packed.String(), nil + } + + switch token.category { + case "strings": + return astBase10to36(token.index), nil + case "integers": + return astBase10to36(token.index + len(oJsonEncode.strings)), nil + case "floats": + return astBase10to36(token.index + len(oJsonEncode.strings) + len(oJsonEncode.integers)), nil + case "boolean": + return fmt.Sprintf("%v", token.index), nil + case "null": + return fmt.Sprintf("%v", TOKEN_NULL), nil + case "empty": + return fmt.Sprintf("%v", TOKEN_EMPTY_STRING), nil + } + + return "", errors.New(fmt.Sprintln("未知类型:", token.category)) +} + +func (oJsonEncode *JsonEncode) toString(token AstToken) (string, error) { + str, err := oJsonEncode.astParser(token) + + if err != nil { + return "", err + } + + var packed strings.Builder + + packed.WriteString(strings.Join(oJsonEncode.strings, "|")) + packed.WriteString("^") + packed.WriteString(strings.Join(oJsonEncode.integers, "|")) + packed.WriteString("^") + packed.WriteString(strings.Join(oJsonEncode.floats, "|")) + packed.WriteString("^") + + packed.WriteString(str) + + return packed.String(), nil +} diff --git a/jsonpack_test.go b/jsonpack_test.go new file mode 100644 index 0000000..52fd2a3 --- /dev/null +++ b/jsonpack_test.go @@ -0,0 +1,86 @@ +package jsonpack + +import ( + "fmt" + "testing" +) + +func TestAstEncode(t *testing.T) { + a := "1+2|3^4%5" + b := "1%2B2%7C3%5E4%255" + + b1 := astEncode(a) + + if b == b1 { + t.Logf("输入=>%s, 输出=>%s", a, b) + } else { + t.Fatalf("输入=>%s, 预计输出=>%s, 实际输出=>%s", a, b, b1) + } + t.Log("done") +} + +func TestAstDecode(t *testing.T) { + a := "1%2B2%7C3%5E4%255" + b := "1+2|3^4%5" + + b1 := astDecode(a) + + if b == b1 { + t.Logf("输入=>%s, 输出=>%s", a, b) + } else { + t.Fatalf("输入=>%s, 预计输出=>%s, 实际输出=>%s", a, b, b1) + } + t.Log("done") +} + +func TestAstBase10to36(t *testing.T) { + a := 65036 + b := "1e6k" + b1 := astBase10to36(a) + + if b == b1 { + t.Logf("输入(10)=>%d, 输出(36)=>%s", a, b) + } else { + t.Fatalf("输入(10)=>%d, 输出(36)=>%s", a, b1) + } + t.Log("done") +} + +func TestAstBase36to10(t *testing.T) { + a := "1e6k" + b := 65036 + b1 := astBase36to10(a) + + if b == b1 { + t.Logf("输入(36)=>%s, 输出(10)=>%d", a, b) + } else { + t.Fatalf("输入(36)=>%s, 输出(10)=>%d", a, b1) + } + t.Log("done") +} + +func TestPack(t *testing.T) { + a := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"} + a1 := []any{"France2", "France1", "1", nil, 123, 123.2222, -15555, -1.2, false, true, a} + + b := "France2|France1|1|France|Paris|Italy|Rome|Japan|Tokyo|India|New+delhi^3f|-c03^123.2222|-1.2^@0|1|2|b|d|c|e|-2|-1|$3|4|5|6|7|8|9|a]]" + + b1, _ := Pack(a1) + + if b == b1 { + t.Logf("\n 输入=>%s \n 输出=>%s", a1, b) + } else { + t.Fatalf("\n 输入=>%s \n 预计输出=>%s \n 实际输出=>%s", a1, b, b1) + } + + t.Log("done") +} + +func TestUnpack(t *testing.T) { + a := "Fran\"ce2|Franc什么e1|1|France|Pa:ris|Italy|Rome|Japan|Tokyo|India|New+delhi^3f|-c03^123.2222|-1.2^@0|1|2|b|d|c|e|-2|-1|$3|4|5|6|7|8|9|a]]" + + b, _ := Unpack(a) + + // fmt.Sprintf("类型: %T, 内容: %+v", b, b) + t.Logf(b) +}