被 go 语言的 json.Marshal 恶心到了

200 天前
 qW7bo2FbzbC0

我的需求是,输入 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 了

13255 次点击
所在节点    Go 编程语言
131 条回复
jsonparse
200 天前
建议用 python
HanSonJ
200 天前
可以用 gjson
NessajCN
200 天前
@qW7bo2FbzbC0 不然列,[]byte 本来就已经是 byte array 了,你再把他序列化一遍想变成啥?
vczyh
200 天前
sql.RawBytes 就是[]byte, []byte 序列化 json 的时候就是应该使用 base64 ,不然控制字符怎么打印?你不能认为都是[]byte{97, 98} ab 这种可打印字符。

不符合你的预期而已,你都用[]byte 接收据了,类型肯定丢失了。
elevioux
200 天前
life is short , use php
james122333
200 天前
@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}
kkk9
200 天前
惯性思维、学艺不精
wysnxzm
200 天前
总结:go 没问题,是你不会自适应
james122333
200 天前
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}
lysShub
200 天前
看你的需求,直接存 string 字符串,查询的时候用 gjson 读。当然这样性能和安全就下降了一个层次
james122333
200 天前
@qW7bo2FbzbC0

我已经回了 n 次 json.RawMessage...
lesismal
200 天前
定义跟 sql table 对应的 struct 是必要的, 自动绑定后用 json 去操作 struct 就没这个问题了.
个人觉得 orm 不好用, 自己搞了份 raw sql+自动绑定的库, 有兴趣可以试试, 例子:
https://github.com/lesismal/sqlw_examples/blob/main/mysql/db/db.go#L101
james122333
200 天前
@lesismal

你写的这个楼主说的 sqlx 已经有了
然后我在公司又把它封装成无 join 功能的 orm
xFrye
200 天前
我觉得用一门语言的原则是用他的推荐方式去实现代码,而不是想着怎么这垃圾语言没法像谁谁那样写。。。
skuuhui
200 天前
interface{}
qW7bo2FbzbC0
200 天前
同样是使用匿名类型去序列化,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();
}

}

}

```
pkoukk
200 天前
map[string]any
然后根据你说的,某个标识字段,判断类型,再用 https://github.com/mitchellh/mapstructure
转成你要的实际 struct
ScepterZ
200 天前
这是 sql 库返回的对象不好序列化的问题,不是 json 库的问题,要解决的话也应该是 sql 库这边来处理,给一个方便打印的办法
qW7bo2FbzbC0
200 天前
@james122333 json.RawMessage 是可行的,但是看起来和 java/c#的模式不同,从 go/sql 映射出来的 interface 变成了[]byte 而不是(interface).type ,而且官方文档好像也推荐用 interface 或者 sql.RawBytes 来接未知类型
qW7bo2FbzbC0
200 天前
@ScepterZ 是的,我说错了,是 go/sql 返回的 interface 无法直接序列化

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/1057942

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX