优化结构

This commit is contained in:
Yvan
2026-08-20 23:02:06 +08:00
parent 63dbb4cfbc
commit e65cf0b1d0
338 changed files with 622 additions and 481 deletions
@@ -0,0 +1,78 @@
package configutil
import (
"encoding/json"
"strings"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
// MergeProtoJSON applies a JSON object as a partial protobuf update while
// retaining fields omitted by the caller.
func MergeProtoJSON(target proto.Message, patch json.RawMessage, options protojson.UnmarshalOptions) error {
currentRaw, err := protojson.MarshalOptions{UseProtoNames: false}.Marshal(target)
if err != nil {
return err
}
var current map[string]any
if err = json.Unmarshal(currentRaw, &current); err != nil {
return err
}
var incoming map[string]any
if err = json.Unmarshal(patch, &incoming); err != nil {
return err
}
incoming = normalizeJSONKeys(incoming).(map[string]any)
mergeJSONObjects(current, incoming)
merged, err := json.Marshal(current)
if err != nil {
return err
}
return options.Unmarshal(merged, target)
}
func normalizeJSONKeys(value any) any {
switch item := value.(type) {
case map[string]any:
result := make(map[string]any, len(item))
for key, nested := range item {
result[snakeToLowerCamel(key)] = normalizeJSONKeys(nested)
}
return result
case []any:
result := make([]any, len(item))
for index, nested := range item {
result[index] = normalizeJSONKeys(nested)
}
return result
default:
return value
}
}
func snakeToLowerCamel(value string) string {
if !strings.Contains(value, "_") {
return value
}
parts := strings.Split(value, "_")
result := parts[0]
for _, part := range parts[1:] {
if part != "" {
result += strings.ToUpper(part[:1]) + part[1:]
}
}
return result
}
func mergeJSONObjects(target, patch map[string]any) {
for key, value := range patch {
if incoming, ok := value.(map[string]any); ok {
if existing, exists := target[key].(map[string]any); exists {
mergeJSONObjects(existing, incoming)
continue
}
}
target[key] = value
}
}
@@ -0,0 +1,28 @@
package configutil
import (
"encoding/json"
"testing"
"kra/internal/conf"
"google.golang.org/protobuf/encoding/protojson"
)
func TestMergeProtoJSONPreservesOmittedFieldsAndNormalizesKeys(t *testing.T) {
target := &conf.AdminBackend{
RouterPrefix: "/api",
System: &conf.AdminBackend_System{UseRedis: true, IplimitCount: 3},
}
patch := json.RawMessage(`{"router_prefix":"/admin","system":{"iplimit_count":9}}`)
if err := MergeProtoJSON(target, patch, protojson.UnmarshalOptions{DiscardUnknown: true}); err != nil {
t.Fatal(err)
}
if target.RouterPrefix != "/admin" || target.System.IplimitCount != 9 {
t.Fatalf("patch was not applied: %#v", target)
}
if !target.System.UseRedis {
t.Fatal("an omitted nested field was cleared")
}
}