Created
Some checks failed
Go / build (push) Failing after 7s

This commit is contained in:
scheibling
2025-04-08 19:16:39 +02:00
commit b4eb50ab55
63 changed files with 7333 additions and 0 deletions

157
jsonx/json.go Normal file
View File

@@ -0,0 +1,157 @@
package jsonx
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"
)
func ToRawMessage(i interface{}, defaultValue string) (json.RawMessage, error) {
m := json.RawMessage{}
var b []byte
var err error
b, err = json.Marshal(&i)
if err != nil {
return m, err
}
b = bytes.TrimSpace(b)
if len(b) == 0 || bytes.EqualFold(b, []byte("null")) {
b = []byte(defaultValue)
}
err = m.UnmarshalJSON(b)
return m, err
}
// ToJson Change interface to json string
func ToJson(i interface{}, defaultValue string) string {
if i == nil {
return defaultValue
}
vo := reflect.ValueOf(i)
switch vo.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
if vo.IsNil() {
return defaultValue
}
default:
}
b, err := json.Marshal(i)
if err != nil {
return defaultValue
}
var buf bytes.Buffer
err = json.Compact(&buf, b)
if err != nil {
return defaultValue
}
if json.Valid(buf.Bytes()) {
return buf.String()
}
return defaultValue
}
func ToPrettyJson(i interface{}) string {
if i == nil {
return "null"
}
vo := reflect.ValueOf(i)
switch vo.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
if vo.IsNil() {
return "null"
}
default:
}
b, err := json.Marshal(i)
if err != nil {
return fmt.Sprintf("%+v", i)
}
var buf bytes.Buffer
err = json.Indent(&buf, b, "", " ")
if err != nil {
return fmt.Sprintf("%+v", i)
}
return buf.String()
}
// EmptyObjectRawMessage 空对象
func EmptyObjectRawMessage() json.RawMessage {
v := json.RawMessage{}
_ = v.UnmarshalJSON([]byte("{}"))
return v
}
// EmptyArrayRawMessage 空数组
func EmptyArrayRawMessage() json.RawMessage {
v := json.RawMessage{}
_ = v.UnmarshalJSON([]byte("[]"))
return v
}
// IsEmptyRawMessage 验证数据是否为空
func IsEmptyRawMessage(data json.RawMessage) bool {
if data == nil {
return true
}
b, err := data.MarshalJSON()
if err != nil {
return true
}
s := string(bytes.TrimSpace(b))
if s == "" || s == "[]" || s == "{}" || strings.EqualFold(s, "null") {
return true
}
if strings.Index(s, " ") != -1 {
s = strings.ReplaceAll(s, " ", "")
}
return s == "[]" || s == "{}"
}
func Convert(from json.RawMessage, to any) error {
if IsEmptyRawMessage(from) {
return nil
}
var b []byte
b, err := from.MarshalJSON()
if err != nil {
return err
}
return json.Unmarshal(b, &to)
}
// Extract 提取字符串中的有效 JSON 数据
// 比如 `{"a": 1, "b": 2}}}}a` 提取后的数据为 `{"a": 1, "b": 2}`
func Extract(str string) (string, error) {
str = strings.TrimSpace(str)
n := len(str)
if n == 0 {
return "", errors.New("jsonx: empty string")
}
if json.Valid([]byte(str)) {
return str, nil
}
for i := 0; i < n; i++ {
if str[i] == '{' || str[i] == '[' {
for j := n; j > i; j-- {
substr := str[i:j]
if json.Valid([]byte(substr)) {
return substr, nil
}
}
}
}
return "", errors.New("jsonx: not found")
}

166
jsonx/json_test.go Normal file
View File

@@ -0,0 +1,166 @@
package jsonx
import (
"encoding/json"
"github.com/stretchr/testify/assert"
"strings"
"testing"
)
func TestToJson(t *testing.T) {
var names []string
testCases := []struct {
Number int
Value interface{}
DefaultValue string
Except string
}{
{1, []string{}, "[]", "[]"},
{2, struct{}{}, "", "{}"},
{3, struct {
Name string
Age int
}{"Hello", 12}, "", `{"Name":"hello","Age":12}`},
{4, struct {
Name string `json:"a"`
Age int `json:"b"`
}{"Hello", 12}, "", `{"a":"hello","b":12}`},
{5, nil, "abc", "abc"},
{6, []int{1, 2}, "null", "[1,2]"},
{7, []string{"a", "b"}, "null", `["a","b"]`},
{8, 1, "[]", "1"},
{9, "abc", "[]", `"abc"`},
{10, nil, "[]", `[]`},
{11, names, "[]", `[]`},
}
for _, testCase := range testCases {
s := ToJson(testCase.Value, testCase.DefaultValue)
if !strings.EqualFold(s, testCase.Except) {
t.Errorf("%d %#v except: %s actual: %s", testCase.Number, testCase.Value, testCase.Except, s)
}
}
}
func TestEmptyObject(t *testing.T) {
result := "{}"
if b, err := EmptyObjectRawMessage().MarshalJSON(); err == nil {
eValue := string(b)
if !strings.EqualFold(eValue, result) {
t.Errorf("Excepted value: %s, actual value: %s", eValue, result)
}
} else {
t.Errorf("Error: %s", err.Error())
}
}
func TestEmptyArray(t *testing.T) {
result := "[]"
if b, err := EmptyArrayRawMessage().MarshalJSON(); err == nil {
eValue := string(b)
if !strings.EqualFold(eValue, result) {
t.Errorf("Excepted value: %s, actual value: %s", eValue, result)
}
} else {
t.Errorf("Error: %s", err.Error())
}
}
func TestIsEmptyRawMessage(t *testing.T) {
type testCase struct {
Number int
Value json.RawMessage
Empty bool
}
v1, _ := ToRawMessage([]string{}, "[]")
v2, _ := ToRawMessage([]string{"a", "b"}, "[]")
v3, _ := ToRawMessage([]int{1, 2, 3}, "[]")
v4, _ := ToRawMessage(struct {
Name string
Age int
}{"John", 10}, "{}")
v5, _ := ToRawMessage(nil, "[]")
a := json.RawMessage{}
a.UnmarshalJSON([]byte("null"))
b := json.RawMessage{}
b.UnmarshalJSON([]byte(""))
c := json.RawMessage{}
c.UnmarshalJSON([]byte("[ ]"))
testCases := []testCase{
{1, json.RawMessage{}, true},
{2, EmptyObjectRawMessage(), true},
{3, EmptyArrayRawMessage(), true},
{4, v1, true},
{5, v2, false},
{6, v3, false},
{7, v4, false},
{8, v5, true},
{9, a, true},
{10, b, true},
{11, c, true},
}
for _, tc := range testCases {
v := IsEmptyRawMessage(tc.Value)
if v != tc.Empty {
t.Errorf("%d except: %v, actual: %v", tc.Number, tc.Empty, v)
}
}
}
func TestConvert(t *testing.T) {
testCases := []struct {
Number int
From json.RawMessage
Except any
}{
{1, nil, struct{}{}},
{2, EmptyArrayRawMessage(), []struct{}{}},
{3, []byte(`{"ID":1,"Name":"hiscaler"}`), struct {
ID int
Name string
}{}},
{4, []byte(`{"ID":1,"Name":"hiscaler","age":1}`), struct {
ID int
Name string
age int
}{}},
}
for _, testCase := range testCases {
exceptValue := testCase.Except
err := Convert(testCase.From, &exceptValue)
assert.Equalf(t, nil, err, "Test %d", testCase.Number)
actualValue := ""
if testCase.From != nil {
actualValue = ToJson(exceptValue, "null")
}
t.Logf(`
#%d %s
%#v`, testCase.Number, testCase.From, exceptValue)
assert.Equalf(t, string(testCase.From), actualValue, "Test %d", testCase.Number)
}
}
func TestExtract(t *testing.T) {
testCases := []struct {
Number int
From string
Except string
HasError bool
}{
{1, "", "", true},
{2, "{}", "{}", false},
{3, " {} ", "{}", false},
{4, `{"a": 1, "b": 2}`, `{"a": 1, "b": 2}`, false},
{5, `{"a": 1, "b": 2}}}}a`, `{"a": 1, "b": 2}`, false},
{6, `{"a": 1, "b": 2}}}}<!--This page has been excluded -->`, `{"a": 1, "b": 2}`, false},
{7, "[]", "[]", false},
{8, "[]1[]2", "[]", false},
}
for _, testCase := range testCases {
exceptValue := testCase.Except
actualValue, err := Extract(testCase.From)
assert.Equalf(t, testCase.HasError, err != nil, "Test HasError %d", testCase.Number)
assert.Equalf(t, exceptValue, actualValue, "Test Value %d", testCase.Number)
}
}

246
jsonx/parser.go Normal file
View File

@@ -0,0 +1,246 @@
package jsonx
import (
"encoding/json"
"reflect"
"strconv"
"strings"
"git.cloudyne.io/go/hiscaler-gox/bytex"
"git.cloudyne.io/go/hiscaler-gox/stringx"
)
// Parser is a json string parse helper and not required define struct.
// You can use Find() method get the path value, and convert to string, int, int64, float32, float64, bool value.
// And you can use Exists() method check path is exists
// Usage:
// parser := jsonx.NewParser("[0,1,2]")
// parser.Find("1").Int() // Return 1, founded
// parser.Find("10", 0).Int() // Return 0 because not found, you give a default value 0
type Parser struct {
data reflect.Value
value reflect.Value
}
type ParseFinder Parser
func (pf ParseFinder) Interface() interface{} {
return pf.value.Interface()
}
func (pf ParseFinder) String() string {
switch pf.value.Kind() {
case reflect.Invalid:
return ""
default:
return stringx.String(pf.value.Interface())
}
}
func (pf ParseFinder) Float32() float32 {
switch pf.value.Kind() {
case reflect.Invalid:
return 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float32(pf.value.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return float32(pf.value.Uint())
case reflect.Float32, reflect.Float64:
return float32(pf.value.Float())
case reflect.Bool:
if pf.value.Bool() {
return 1
}
return 0
case reflect.String:
d, err := strconv.ParseFloat(pf.value.String(), 32)
if err != nil {
return 0
}
return float32(d)
default:
return 0
}
}
func (pf ParseFinder) Float64() float64 {
switch pf.value.Kind() {
case reflect.Invalid:
return 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(pf.value.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return float64(pf.value.Uint())
case reflect.Float32, reflect.Float64:
return pf.value.Float()
case reflect.Bool:
if pf.value.Bool() {
return 1
}
return 0
case reflect.String:
d, _ := strconv.ParseFloat(pf.value.String(), 64)
return d
default:
return 0
}
}
func (pf ParseFinder) Int() int {
switch pf.value.Kind() {
case reflect.Invalid:
return 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return int(pf.value.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return int(pf.value.Uint())
case reflect.Float32, reflect.Float64:
return int(pf.value.Float())
case reflect.Bool:
if pf.value.Bool() {
return 1
}
return 0
case reflect.String:
d, _ := strconv.Atoi(pf.value.String())
return d
default:
return 0
}
}
func (pf ParseFinder) Int64() int64 {
switch pf.value.Kind() {
case reflect.Invalid:
return 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return pf.value.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return int64(pf.value.Uint())
case reflect.Float32, reflect.Float64:
return int64(pf.value.Float())
case reflect.Bool:
if pf.value.Bool() {
return 1
}
return 0
case reflect.String:
d, err := strconv.ParseInt(pf.value.String(), 10, 64)
if err != nil {
return 0
}
return d
default:
return 0
}
}
func (pf ParseFinder) Bool() bool {
switch pf.value.Kind() {
case reflect.Invalid:
return false
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return pf.value.Int() > 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return pf.value.Uint() > 0
case reflect.Float32, reflect.Float64:
return pf.value.Float() > 0
case reflect.Bool:
return pf.value.Bool()
case reflect.String:
v, _ := strconv.ParseBool(pf.value.String())
return v
default:
return false
}
}
func getElement(v reflect.Value, p string) reflect.Value {
switch v.Kind() {
case reflect.Map:
vv := v.MapIndex(reflect.ValueOf(p))
if vv.Kind() == reflect.Interface {
vv = vv.Elem()
}
return vv
case reflect.Array, reflect.Slice:
if i, err := strconv.Atoi(p); err == nil {
if i >= 0 && i < v.Len() {
v = v.Index(i)
for v.Kind() == reflect.Interface {
v = v.Elem()
}
return v
}
}
}
return reflect.Value{}
}
func NewParser(s string) *Parser {
p := &Parser{}
return p.LoadString(s)
}
func (p *Parser) LoadString(s string) *Parser {
return p.LoadBytes(stringx.ToBytes(s))
}
func (p *Parser) LoadBytes(bytes []byte) *Parser {
if bytex.IsBlank(bytes) {
return p
}
var sd interface{}
if err := json.Unmarshal(bytes, &sd); err != nil {
return p
}
p.data = reflect.ValueOf(sd)
return p
}
func (p Parser) Exists(path string) bool {
if !p.data.IsValid() || path == "" {
return false
}
data := p.data
parts := strings.Split(path, ".")
n := len(parts)
for i := 0; i < n; i++ {
if data = getElement(data, parts[i]); !data.IsValid() {
return false
}
if i == n-1 {
// is last path
return true
}
}
return false
}
func (p *Parser) Find(path string, defaultValue ...interface{}) *ParseFinder {
if len(defaultValue) > 0 {
p.value = reflect.ValueOf(defaultValue[0])
}
if !p.data.IsValid() || path == "" {
return (*ParseFinder)(p)
}
data := p.data
// find the value corresponding to the path
// if any part of path cannot be located, return the default value
parts := strings.Split(path, ".")
n := len(parts)
for i := 0; i < n; i++ {
if data = getElement(data, parts[i]); !data.IsValid() {
return (*ParseFinder)(p)
}
if i == n-1 {
// is last path
p.value = data
}
}
return (*ParseFinder)(p)
}

78
jsonx/parser_test.go Normal file
View File

@@ -0,0 +1,78 @@
package jsonx
import (
"github.com/stretchr/testify/assert"
"reflect"
"testing"
)
func TestParser_Find(t *testing.T) {
testCases := []struct {
tag string
json string
path string
defaultValue interface{}
valueKind reflect.Kind
Except interface{}
}{
{"string1", "", "a", "", reflect.String, ""},
{"string2", `{"a":1}`, "a", 2, reflect.String, "1"},
{"string3", `{"a":true}`, "a", 2, reflect.String, "true"},
{"string4", `{"a":true}`, "a.b", false, reflect.String, "false"},
{"string5", `{"a":{"b": {"c": 123}}}`, "a.b", "{}", reflect.String, `{"c":123}`},
{"string6", `{"a":{"b": {"c": 123}}}`, "a.b.c", "", reflect.String, "123"},
{"string7", `{"a":{"b": {"c": [1,2,3]}}}`, "a.b.c.0", "", reflect.String, "1"},
{"string8", `{"a":{"b": {"c": [1,2,3]}}}`, "a.b.c.2", "", reflect.String, "3"},
{"string9", `{"a":{"b": {"c": [1,2,3]}}}`, "", "110", reflect.String, "110"},
{"int1", `{"a":1}`, "a", 2, reflect.Int, 1},
{"int2", `{"a":1}`, "aa", 2, reflect.Int, 2},
{"int641", `{"a":1}`, "a", 2, reflect.Int64, int64(1)},
{"int641", `{"a":1}`, "aa", 2, reflect.Int64, int64(2)},
{"bool1", `{"a":true}`, "a", false, reflect.Bool, true},
{"bool2", `{"a":true}`, "a.b", false, reflect.Bool, false},
{"float321", `{"a":1.23}`, "a", 0, reflect.Float32, float32(1.23)},
{"float322", `{"a":1.23}`, "b", 0, reflect.Float32, float32(0)},
{"float641", `{"a":1.23}`, "a", 0, reflect.Float64, 1.23},
{"float642", `{"a":1.23}`, "b", 0, reflect.Float64, 0.0},
{"interface1", `{"a":1.23}`, "b", 0, reflect.Interface, 0},
{"interface2", `null`, "b", 0, reflect.Interface, 0},
}
for _, testCase := range testCases {
var v interface{}
switch testCase.valueKind {
case reflect.String:
v = NewParser(testCase.json).Find(testCase.path, testCase.defaultValue).String()
case reflect.Int:
v = NewParser(testCase.json).Find(testCase.path, testCase.defaultValue).Int()
case reflect.Int64:
v = NewParser(testCase.json).Find(testCase.path, testCase.defaultValue).Int64()
case reflect.Float32:
v = NewParser(testCase.json).Find(testCase.path, testCase.defaultValue).Float32()
case reflect.Float64:
v = NewParser(testCase.json).Find(testCase.path, testCase.defaultValue).Float64()
case reflect.Bool:
v = NewParser(testCase.json).Find(testCase.path, testCase.defaultValue).Bool()
case reflect.Interface:
v = NewParser(testCase.json).Find(testCase.path, testCase.defaultValue).Interface()
}
assert.Equal(t, testCase.Except, v, testCase.tag)
}
}
func TestParser_Exists(t *testing.T) {
testCases := []struct {
tag string
json string
path string
Except bool
}{
{"exists1", "", "a", false},
{"exists2", `{"a"}`, "a", false},
{"exists3", `{"a":1}`, "a", true},
{"exists4", `{"a":[0,1,2]}`, "a.1", true},
}
for _, testCase := range testCases {
v := NewParser(testCase.json).Exists(testCase.path)
assert.Equal(t, testCase.Except, v, testCase.tag)
}
}