我的需求是,输入 sql ,返回序列化后的 json 结果。
在 python 中,官方库就可以返回[{'col1': 1, 'col2': '2', 'col3': true}....]
这种带类型的 json 结果。
在 go 中如果已知 sql 返回的列数和列类型,也可以构造一个 struct 进行数据映射,然后用json.Marshal
转为 json 。
如果 sql 返回的列数和行类型未知,就很难受了,在 go/mysql 的官方 wiki 案例 中对于匿名的结果,使用了 interface 或者 sql.RawBytes ,但这两种替代方式在json.Marshal
后都变成了 base64 encode 后的 string ,既丢失了类型也变异了结果( https://stackoverflow.com/questions/32501784/the-sqlx-library-gives-weird-base64-encoded-looking-results)
请问各位在实际业务中遇到这个问题是怎么处理的?
在其他语言中很自然的 object 序列化为原类型,在 go 的 json.Marshal 中怎么就全变成 string 了
1
yrzs 139 天前
|
2
FrankFang128 139 天前
没遇到过,没看懂
|
3
3img 139 天前
如果是不知道指针的类型,可以用反射啊
|
4
dzdh 139 天前 1
我翻译一下。
up 想要 go 实现 php 的 json_encode(PDO->fethcAll()) 不定义 struct |
5
NessajCN 139 天前
首先 Marshal 是序列化,Unmarshal 是反序列化, 也就是从 byte 到 go struct 应该用 Unmarshal
其次,对于未知的返回类型一般不 Unmarshal , 直接从 interface 里取键值,go 自带返回 ok 判断是否有这个 key 譬如 ```go type SqlResult map[string]interface{} var res SqlResult if col1,ok := res["col1"].(int32); ok { //... } ``` |
6
povsister 139 天前
你适合用 python
|
7
sagaxu 139 天前
根据 ColumnType 判断一下字段类型,自己写个转换,把[]byte 和[]uint8 都转成 string
|
8
laikick 139 天前
你适合用 PHP
|
9
zxdstyle 139 天前
这不是 go 和 python 的区别,是强类型语言与弱类型语言的区别
|
12
james122333 139 天前 via Android
你可以用 map[string]any 或 json.RawMessage
|
13
james122333 139 天前 via Android
map[string]any, []any...族繁不及备载
|
14
Nazz 139 天前
要修改下结构体定义, 例如把 []uint8 换成 []uint16
|
15
james122333 139 天前 via Android
看 col 举例应该是[]map[string]any
|
16
ChrisFreeMan 139 天前
OP 赶紧跑吧你完了
|
17
luhengyuorang 139 天前
|
18
dcalsky 139 天前 11
收收 python 、js 、php 味。go 的正规业务里,都要得先定义 struct ,不到万不得已不用 map[string]any
|
19
liaohongxing 139 天前
总结不适应强类型
|
20
rekulas 139 天前
go 真冤 完全没看懂这跟 go 有什么关系
|
21
qW7bo2FbzbC0 OP |
22
qW7bo2FbzbC0 OP @sagaxu @NessajCN @james122333 但是 go 语言中,用 interface 或者 any 来模拟 java c#这种 getObject ,然后序列化,结果就是 base64 encode 后的 string
|
23
cuiweiqiang 139 天前
既然用 sql 了,为啥还会存在“行类型未知”这种场景?
|
24
james122333 139 天前 via Android
是最好定义 struct 外加上泛型
|
25
qW7bo2FbzbC0 OP @dcalsky 肯定是有传入黑箱 sql ,返回 json 结果的场景,没遇到不等于没有。这和 python 、js 、php 味没关系,java c#都正常
|
26
wildlife 139 天前
标准的 JSON 不是只能使用 "" 双引号包围的任意数量 Unicode 字符的集合吗?
|
27
qW7bo2FbzbC0 OP @cuiweiqiang 用 java 模拟 mysql 命令行工具,这个场景是不是 sql 已知,行类型未知?
|
28
james122333 139 天前 via Android
|
29
xingjue 139 天前
你适合用 PHP 典型的脚本语言思想
|
30
james122333 139 天前 via Android
阿... Marshal 的话就直接 Marshal 成 json.RawMessage 就好了
把字段定义成 json.RawMessage 即可 |
31
MoYi123 139 天前 1
@qW7bo2FbzbC0 怎么可能是 base64 后的 string 啊, 贴个例子看看. 八成是 unicode 吧, 提醒一下, json 必须是 utf8 编码的.
|
32
qW7bo2FbzbC0 OP |
33
qW7bo2FbzbC0 OP https://forum.golangbridge.org/t/json-encode-byte-array-as-hex-string-instead-of-base64/26751
``` One must use reflection to walk and copy the original object replacing byte arrays with the user-defined type for which the MarshalJSON method is defined, but there’s probably no better way. An even uglier alternative is to post-process the JSON. ``` |
34
fregie 139 天前
犯懒就用 js python 就好了,何必选个强类型语言给自己找罪受呢
|
35
qW7bo2FbzbC0 OP |
36
Felldeadbird 139 天前
用一门新语言时,最怕就是拿别的语言对比新学的语言。
不想麻烦就定义结构体,一次定义全代码收益,要像脚本语言那么自然,就得多写一些代码转换。 |
37
qW7bo2FbzbC0 OP @fregie 1 、项目定型是这个语言。2 、类似的蹩脚问题不止这一个,在 go1.8 之前代码里面好多 inteface 飞来飞去,不仅丢失了类型还影响类型安全。 我讨厌 interface 转来转去也是犯懒,我觉得 go/sql 和 json.Marshal 对于匿名类型查询不友好也是犯懒?你扣帽子是什么态度?
|
38
cs8425 139 天前
@qW7bo2FbzbC0 因为 json 的 string 必须为 UTF-8 而[]byte 不一定是 UTF-8
|
39
Jinnrry 139 天前 via Android
你可以试试 map any any
不过建议你写 php |
40
qW7bo2FbzbC0 OP 同样是使用匿名类型去序列化,c#正确识别出了 Number 类型
```c# // See https://aka.ms/new-console-template for more information using System.Text.Json; using MySqlConnector; using var connection = new MySqlConnection("Server=127.0.0.1;User ID=root;Password=****;Database=test"); connection.Open(); using var command = new MySqlCommand("select id from t1;", connection); using var reader = command.ExecuteReader(); var x = new Dictionary<string, object> { }; while (reader.Read()) { x["name"] = "name"; x["value"] = reader.GetValue(0); } Console.WriteLine(JsonSerializer.Serialize(x)); ``` 输出结果为 ```js {"name":"name","value":1} ``` |
41
jsonparse 139 天前
建议用 python
|
42
HanSonJ 139 天前
可以用 gjson
|
43
NessajCN 139 天前
@qW7bo2FbzbC0 不然列,[]byte 本来就已经是 byte array 了,你再把他序列化一遍想变成啥?
|
44
vczyh 139 天前
sql.RawBytes 就是[]byte, []byte 序列化 json 的时候就是应该使用 base64 ,不然控制字符怎么打印?你不能认为都是[]byte{97, 98} ab 这种可打印字符。
不符合你的预期而已,你都用[]byte 接收据了,类型肯定丢失了。 |
45
elevioux 139 天前
life is short , use php
|
46
james122333 139 天前
@qW7bo2FbzbC0
package main import( "encoding/json" ) func main() { m := map[string]any { "name": "name", "value": 1, } b, _ := json.Marshal(m) println(string(b)) } {"name":"name","value":1} |
47
kkk9 139 天前 2
惯性思维、学艺不精
|
48
wysnxzm 139 天前
总结:go 没问题,是你不会自适应
|
49
james122333 139 天前
package main
import( "encoding/json" ) func main() { m := map[string]any { "name": "name", "value": json.RawMessage([]byte("1")), } b, _ := json.Marshal(m) println(string(b)) } {"name":"name","value":1} |
50
lysShub 139 天前
看你的需求,直接存 string 字符串,查询的时候用 gjson 读。当然这样性能和安全就下降了一个层次
|
51
james122333 139 天前
|
52
lesismal 139 天前
定义跟 sql table 对应的 struct 是必要的, 自动绑定后用 json 去操作 struct 就没这个问题了.
个人觉得 orm 不好用, 自己搞了份 raw sql+自动绑定的库, 有兴趣可以试试, 例子: https://github.com/lesismal/sqlw_examples/blob/main/mysql/db/db.go#L101 |
53
james122333 139 天前 via Android
|
54
xFrye 139 天前 4
我觉得用一门语言的原则是用他的推荐方式去实现代码,而不是想着怎么这垃圾语言没法像谁谁那样写。。。
|
55
skuuhui 139 天前
interface{}
|
56
qW7bo2FbzbC0 OP 同样是使用匿名类型去序列化,java 也正确识别出了 Number 类型
```java package com.test.Demo; import java.sql.*; import java.io.*; import java.util.*; import com.google.gson.*; public class Demo { static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver"; static final String DB_URL = "jdbc:mysql://127.0.0.1:3306/test?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"; static final String USER = "root"; static final String PASS = "******"; public static void main(String[] args) { Connection conn = null; Statement stmt = null; try{ Class.forName(JDBC_DRIVER); conn = DriverManager.getConnection(DB_URL,USER,PASS); stmt = conn.createStatement(); String sql = "select id from t1"; ResultSet rs = stmt.executeQuery(sql); HashMap<String, Object> x; x = new HashMap<String, Object>(); while(rs.next()){ Object id = rs.getObject("id"); x.put("name", "name"); x.put("value", id); } Gson gson = new Gson(); System.out.println(gson.toJson(x)); } catch (Exception e){ e.printStackTrace(); } } } ``` |
57
pkoukk 139 天前
|
58
ScepterZ 139 天前
这是 sql 库返回的对象不好序列化的问题,不是 json 库的问题,要解决的话也应该是 sql 库这边来处理,给一个方便打印的办法
|
59
qW7bo2FbzbC0 OP @james122333 json.RawMessage 是可行的,但是看起来和 java/c#的模式不同,从 go/sql 映射出来的 interface 变成了[]byte 而不是(interface).type ,而且官方文档好像也推荐用 interface 或者 sql.RawBytes 来接未知类型
|
60
qW7bo2FbzbC0 OP @ScepterZ 是的,我说错了,是 go/sql 返回的 interface 无法直接序列化
|
62
houzhiqiang 139 天前
我猜想要的效果是这样的:
```python import pymysql import pymysql.cursors connect = pymysql.connect( host="example.com", port=3306, user="user", password="password", database="database_name", cursorclass=pymysql.cursors.DictCursor, ) with connect.cursor() as cursor: cursor.execute( """select * from users limit %(param_limit)s""", args={"param_limit": 2} ) results = cursor.fetchall() print(results) # [ # {'id': 13, 'birthday': datetime.date(1900, 1, 1), 'create_time': datetime.datetime(2020, 9, 3, 16, 56, 39)}, # {'id': 39, 'birthday': datetime.date(1900, 1, 1), 'create_time': datetime.datetime(2020, 9, 14, 17, 5, 1)} # ] ``` |
63
vczyh 139 天前
@qW7bo2FbzbC0
func TestMy(t *testing.T) { db, err := sql.Open("mysql", "root:123@/test") if err != nil { t.Fatal(err) } defer db.Close() rows, err := db.Query("SELECT id FROM t1") if err != nil { t.Fatal(err) } for rows.Next() { var id any if err := rows.Scan(&id); err != nil { t.Fatal(err) } m := map[string]any{ "value": id, } b, err := json.Marshal(m) if err != nil { t.Fatal(err) } t.Log(string(b)) } if err := rows.Err(); err != nil { t.Fatal(err) } } {"value":1} 用 any 接收数据,返回的类型由驱动实现决定,database/sql 只提供接口,驱动负责返回 sqldriver.Value ,所以真不是 Go 语言的问题。 |
64
jlkm2010 139 天前
go 的语法很怪异
|
65
kkbblzq 139 天前
这跟 json 都没有关系,而是 sql 类型和 go 类型之间的转换问题。
|
66
qW7bo2FbzbC0 OP @vczyh 你这个是什么驱动?
|
67
vczyh 139 天前
|
68
vczyh 139 天前
|
69
lesismal 139 天前
@james122333
1. orm 是非常不好的选择, 中小项目倒是还可以, 大项目大数据量的, 用 orm 存在一些不确定性可能导致性能风险, 所以我都是禁止团队试用 golang orm 的 2. 还有生成代码的 sqlc 这种, 性能当然是最友好但用起来也有点麻烦, 因为一些业务是上下文比较复杂的, 单纯生成一段 sql 对应的 go 代码还需要 ctrl cv 而且还要修改对应的地方, 功能修改的时候比较麻烦 3. sqlx 简化了一些 binding, 但也把一些简单的复杂化了, 比如又有 Select 又有 Get, 比如 Insert 缺少 struct 的自动绑定(我没深入使用只是看它文档和例子), 综合下来我觉得它少了一些可以真正节约体力的但多了不少没必要的, 所以对我来说病不好用 所以以前还是继续标准库 raw sql, 直到我实在忍不了标准库 raw sql 自己搞了这个 |
70
lifei6671 139 天前
@qW7bo2FbzbC0 #37 没看到别人扣帽子,反而是你自己情绪化,人家都说了你用不惯 go 就换成你喜欢的语言好了,再说了,interface 满天飞就是犯懒的行为。
|
71
qW7bo2FbzbC0 OP @lifei6671 1.8 之前还有什么办法避免 Interface 满天飞?
|
72
lesismal 139 天前
@james122333 我这个就是简简单单几点:
1. 鼓励明确定义 table 对应的 struct, 用明确定义的 struct 操作 sql, 这个 struct 自己手写也好用工具生成也好, 都挺方便的 2. 鼓励 raw sql, 避免 orm 之类的可能生成了性能不友好的语句造成性能事故 3. 框架尽量自动映射/绑定 sql table 和 struct, insert/update/select 这些与 struct 映射绑定操作密切的都很轻松用 struct 操作 4. 奥卡姆剃刀原则, 不提供 orm, 不提供那么多没什么必要的垃圾接口 |
73
fregie 139 天前
@qW7bo2FbzbC0 "我觉得 go/sql 和 json.Marshal 对于匿名类型查询不友好也是犯懒?"
不友好的意思是指不能用方便又稳定的办法解决这个问题,只能用麻烦但是稳定的方式解决. 你认为这不友好难道不是犯懒吗? BTW,何必这么敏感,犯懒又不是什么攻击性的词语,我自己也经常犯懒,人皆是如此,只是要可能会付出代价 这个世界还是挺美好的,没必要一进入互联网就进入攻击模式 |
74
superrichman 139 天前
@zxdstyle 你想表达的是动态语言和静态语言的区别
|
75
qW7bo2FbzbC0 OP @fregie 犯懒字面上是贬义词吧,你觉得不是攻击性的,那你可以自己多读几遍,不要替别人做决定。稳定不稳定不知道,写起来麻烦的代码后面维护起来肯定更麻烦
|
76
lesismal 139 天前
@qW7bo2FbzbC0 #71
interface 满天飞是当时写这些垃圾代码的人的问题, 不是 golang 自己的问题. 正确姿势本来就应该避免 interface 满天飞, 我看到很多喜欢搞 interface 满天飞的, 是 php nodejs 之类的转 go 的人居多, 这也不能全怪他们, 毕竟以前语言习惯已经信手拈来了, 刚开始未必能意识到这样会带来工程上的问题, 更何况有时候赶工只追求先搞定业务 c/cpp 或者一些新手上路就是 go 的, 姿势正常的人多些 接手别人的屎山不是 OP 的错, interface 满天飞不是 golang 的错, OP 要怪就都怪写屎山的人吧 |
77
leonshaw 139 天前
用 ScanType 反射?
|
78
iyaozhen 139 天前
用 go 千万不要和别的语音比 不要之前语言思维定势。不然坑还更多
|
79
qW7bo2FbzbC0 OP 按楼上说的 json.RawMessage 解决了,我没有想到要去 json 命名空间里找类型
@leonshaw |
80
james122333 139 天前
|
81
james122333 139 天前 1
@lesismal
我目前弄的只有简单的 crud 和建立表 join 没整其他指令也没整 还是基於 sqlx 整的 只是用原生的 sql 应该也都差不多 根据 struct 中 tag 讯息拼接字符串 反射方法都已经写好 验证下来没问题 会这样搞主要是想偷懒点 |
82
fregie 139 天前
@qW7bo2FbzbC0 嫌麻烦而想放弃,这不是犯懒?
|
83
gowk 139 天前
回帖不支持代码,,看着就非常难受!
|
84
zzhaolei 139 天前
show me the code
|
85
lloovve 139 天前 via iPhone
这边建议转 rust 试试
|
86
woodfizky 139 天前
Python 是强类型语言,不管用 ORM 还是原生 SQL 一样要处理类型问题。
不懂某些评论为什么要说有 Python 味,吐槽之前好好调查一下再给出观点很难是吗? |
87
zzhaolei 139 天前
@lesismal
“1. orm 是非常不好的选择, 中小项目倒是还可以, 大项目大数据量的, 用 orm 存在一些不确定性可能导致性能风险, 所以我都是禁止团队试用 golang orm 的” 这一点可以举例说明:有哪些不确定性可能导致性能风险吗 我现在在公司用的就是 GORM ,想了解一下 |
88
knva 139 天前
go 的 sql set 0 的问题太带劲了
|
89
qW7bo2FbzbC0 OP @fregie 你满意就好
|
90
boqiqita 139 天前
幸好不会用 java 、C#、C 、C++
|
91
XyIsMy 139 天前
|
92
picone 139 天前
Go 使用 json.RawMessage 也可以达到你说的效果,不会变成 base64
|
93
zzhaolei 139 天前
@XyIsMy 我是这么用的。
我就是好奇,这些人张嘴闭嘴就是 ORM 有不确定性,到底有什么不确定性,自己测试过?还是踩过坑?还是说嘴一张一闭反正我不管,ORM 就是有性能问题? 我之前进行过性能测试,如果 GORM 那些事务什么的全都关了,是会比原生 SQL 慢一些,但是没有慢特别多。至今没有踩过什么坑。 |
94
james122333 139 天前
@zzhaolei
orm 本身没有不确定性 本质还是产生 query string 传参 orm 的不确定性在於框架的实现 太过细节的框架从另外角度讲是坑 gorm 确实是慢没错 更何况就是要用事务 我离职了再写一个比较好的自己用 |
95
qW7bo2FbzbC0 OP 各位,我比对了下,[]interface{}没有被 json.Marshal 出正确的数值和类型,是与我用的 go-mysql-driver 版本有关。
1.6.0 版本是出现了与我预测不同的结果。1.8 版本出现了预测的结果 json.Marshal 可能与本次贴文无直接关系。 另外,我觉得问题出自 json.Marshal 是从这个 sof 链接中的评论得到的错误推论,的确有迷惑性( https://stackoverflow.com/questions/34089750/marshal-byte-to-json-giving-a-strange-string) ``` This is because some idiot Millennial at Google decided it. Breaking the behaviour the RFC their JSON impl claims to follow defines. Check stackoverflow.com/a/78662958/3768429 for details. ``` 该文指出 json.Marshal 处理 uint8[]时,错误输出了文字, 验证代码如下 ``` func main() { var x = []uint8{1, 2, 3, 4, 5, 6} var y = []int8{1, 2, 3, 4, 5, 6} xBytes, err := json.Marshal(x) if err != nil { panic(err) } yBytes, err := json.Marshal(y) if err != nil { panic(err) } fmt.Println(fmt.Sprintf("uint8 %s, int8: %s", string(xBytes), string(yBytes))) } ``` 输出的结果为 ``` uint8 "AQIDBAUG", int8: [1,2,3,4,5,6] ``` https://go.dev/play/p/KGNG6voRuDk |
96
sagaxu 139 天前
|
97
qW7bo2FbzbC0 OP @james122333 您是对的,这的确和 json.Marshal 没关系,是 go-mysql-driver 的问题。
https://github.com/go-sql-driver/mysql/pull/1424 |
98
james122333 139 天前
|
99
james122333 139 天前 via Android
|
100
ClericPy 139 天前
以前是用别的语言的吧,我记得有个库可以用比原生的优雅一点,不习惯的话要不要问问别人怎么用的再来吐槽
话说对于 JSON 解析如果没啥特殊需求,大多数语言都有 jmespath 的分支吧,以前用 py 的版本发现性能秒杀同类 jsonpath/objectpath 而且语法也简单,没必要自己硬抠嵌套结构,json-handler 一开直接提取路径了 |