Skip to content

Commit 69c46b4

Browse files
committed
feat: 添加JSON到Proto文件生成器
1 parent 9c6c33f commit 69c46b4

File tree

6 files changed

+255
-1
lines changed

6 files changed

+255
-1
lines changed
+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package gen
2+
3+
import (
4+
"fmt"
5+
"github.com/jhump/protoreflect/desc/builder"
6+
"github.com/jhump/protoreflect/desc/protoprint"
7+
"github.com/tidwall/gjson"
8+
"strings"
9+
"unicode"
10+
)
11+
12+
func Generate(packageName, rootMessage string, rawJson []byte) (string, error) {
13+
14+
rootMsg := builder.NewMessage(rootMessage)
15+
16+
parseJSON("", gjson.ParseBytes(rawJson), rootMsg, make(map[string]*builder.MessageBuilder))
17+
18+
fileBuilder := builder.NewFile(packageName).
19+
SetPackageName(packageName).
20+
AddMessage(rootMsg)
21+
22+
fileBuilder.SetProto3(true)
23+
24+
printer := protoprint.Printer{
25+
Indent: " ",
26+
Compact: true,
27+
}
28+
29+
fd, err := fileBuilder.Build()
30+
if err != nil {
31+
return "", err
32+
}
33+
34+
return printer.PrintProtoToString(fd)
35+
}
36+
37+
func parseJSON(prefix string, result gjson.Result, msgBuilder *builder.MessageBuilder, messageCache map[string]*builder.MessageBuilder) {
38+
if !result.IsObject() {
39+
return
40+
}
41+
42+
result.ForEach(func(key, value gjson.Result) bool {
43+
fieldName := toSnakeCase(key.String())
44+
45+
switch {
46+
case value.IsObject():
47+
nestedMsgName := strings.Title(key.String())
48+
var nestedMsg *builder.MessageBuilder
49+
50+
if cached, ok := messageCache[nestedMsgName]; ok {
51+
nestedMsg = cached
52+
} else {
53+
nestedMsg = builder.NewMessage(nestedMsgName)
54+
messageCache[nestedMsgName] = nestedMsg
55+
parseJSON(prefix+key.String()+".", value, nestedMsg, messageCache)
56+
msgBuilder.AddNestedMessage(nestedMsg)
57+
}
58+
59+
msgBuilder.AddField(
60+
builder.NewField(fieldName, builder.FieldTypeMessage(nestedMsg)),
61+
)
62+
63+
case value.IsArray():
64+
if len(value.Array()) > 0 {
65+
firstElement := value.Array()[0]
66+
if firstElement.IsObject() {
67+
68+
nestedMsgName := strings.Title(strings.TrimSuffix(key.String(), "s"))
69+
var nestedMsg *builder.MessageBuilder
70+
71+
if cached, ok := messageCache[nestedMsgName]; ok {
72+
nestedMsg = cached
73+
} else {
74+
nestedMsg = builder.NewMessage(nestedMsgName)
75+
messageCache[nestedMsgName] = nestedMsg
76+
parseJSON(prefix+key.String()+".", firstElement, nestedMsg, messageCache)
77+
msgBuilder.AddNestedMessage(nestedMsg)
78+
}
79+
80+
msgBuilder.AddField(
81+
builder.NewField(fieldName, builder.FieldTypeMessage(nestedMsg)).
82+
SetRepeated(),
83+
)
84+
} else {
85+
msgBuilder.AddField(
86+
builder.NewField(fieldName, getProtoType(firstElement)).
87+
SetRepeated(),
88+
)
89+
}
90+
}
91+
92+
default:
93+
msgBuilder.AddField(
94+
builder.NewField(fieldName, getProtoType(value)),
95+
)
96+
}
97+
return true
98+
})
99+
}
100+
101+
func getProtoType(value gjson.Result) *builder.FieldType {
102+
switch {
103+
case gjson.String == value.Type:
104+
return builder.FieldTypeString()
105+
case gjson.Number == value.Type:
106+
if value.Raw == fmt.Sprintf("%.0f", value.Float()) {
107+
return builder.FieldTypeInt64()
108+
}
109+
return builder.FieldTypeDouble()
110+
case value.IsBool():
111+
return builder.FieldTypeBool()
112+
default:
113+
return builder.FieldTypeString()
114+
}
115+
}
116+
117+
func toSnakeCase(s string) string {
118+
var result strings.Builder
119+
for i, r := range s {
120+
if i > 0 && r >= 'A' && r <= 'Z' {
121+
result.WriteRune('_')
122+
}
123+
result.WriteRune(unicode.ToLower(r))
124+
}
125+
return result.String()
126+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package gen
2+
3+
import (
4+
"log"
5+
"testing"
6+
)
7+
8+
func TestGenerate(t *testing.T) {
9+
jsonStr := `
10+
{
11+
"userId": "123456",
12+
"userName": "John Doe",
13+
"age": 30,
14+
"isActive": true,
15+
"score": 95.5,
16+
"address": {
17+
"street": "123 Main St",
18+
"city": "New York",
19+
"zipCode": "10001"
20+
},
21+
"tags": ["user", "active", "premium"],
22+
"preferences": [
23+
{
24+
"theme": "dark",
25+
"notifications": true
26+
}
27+
]
28+
}`
29+
text, err := Generate("config", "config.proto", "Config", []byte(jsonStr))
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
log.Printf("Generated proto:\n%s", text)
34+
}

contrib/json-gen-proto/go.mod

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module github.com/TBXark/sphere/contrib/json-gen-proto
2+
3+
go 1.23.2
4+
5+
require (
6+
github.com/jhump/protoreflect v1.17.0
7+
github.com/tidwall/gjson v1.18.0
8+
)
9+
10+
require (
11+
github.com/bufbuild/protocompile v0.14.1 // indirect
12+
github.com/golang/protobuf v1.5.4 // indirect
13+
github.com/tidwall/match v1.1.1 // indirect
14+
github.com/tidwall/pretty v1.2.0 // indirect
15+
google.golang.org/protobuf v1.34.2 // indirect
16+
)

contrib/json-gen-proto/go.sum

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
2+
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
6+
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
7+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
8+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
9+
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
10+
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
11+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
12+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
13+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
14+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
15+
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
16+
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
17+
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
18+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
19+
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
20+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
21+
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
22+
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
23+
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
24+
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
25+
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
26+
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
27+
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
28+
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
29+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
30+
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
31+
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
32+
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
33+
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
34+
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
35+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
36+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

contrib/json-gen-proto/main.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"github.com/TBXark/sphere/contrib/json-gen-proto/gen"
6+
"log"
7+
"os"
8+
)
9+
10+
func main() {
11+
jsonFilePath := flag.String("input", "", "json file path")
12+
protoFilePath := flag.String("output", "", "proto file path")
13+
packageName := flag.String("package", "", "package name")
14+
rootMessage := flag.String("message", "", "root message name")
15+
help := flag.Bool("help", false, "help")
16+
flag.Parse()
17+
18+
if *help {
19+
flag.PrintDefaults()
20+
return
21+
}
22+
23+
if *jsonFilePath == "" || *protoFilePath == "" || *packageName == "" || *rootMessage == "" {
24+
flag.PrintDefaults()
25+
return
26+
}
27+
28+
file, err := os.ReadFile(*jsonFilePath)
29+
if err != nil {
30+
log.Fatalf("read json file error: %v", err)
31+
}
32+
33+
res, err := gen.Generate(*packageName, *rootMessage, file)
34+
if err != nil {
35+
log.Fatalf("generate proto error: %v", err)
36+
}
37+
38+
err = os.WriteFile(*protoFilePath, []byte(res), 0644)
39+
if err != nil {
40+
log.Fatalf("write proto file error: %v", err)
41+
}
42+
}

contrib/sphere-cli/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module github.com/TBXark/sphere/contrib/sphere-cli
22

3-
go 1.23
3+
go 1.23.2

0 commit comments

Comments
 (0)