V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
qW7bo2FbzbC0
V2EX  ›  Go 编程语言

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

  •  
  •   qW7bo2FbzbC0 · 127 天前 · 12046 次点击
    这是一个创建于 127 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    第 2 条附言  ·  120 天前
    131 条回复    2024-10-22 17:49:42 +08:00
    1  2  
    ly020044
        101
    ly020044  
       127 天前
    看一下 ```json.Marshaler``` 这个接口
    ly020044
        102
    ly020044  
       127 天前
    看一下 json.Marshaler 这个接口
    hekkowoerld
        103
    hekkowoerld  
       127 天前   ❤️ 1
    与其说是其他人在这攻击楼主不会用,倒是楼主积极探索的态度值得鼓励,本人也觉得这个用法很蹩脚。
    jc89898
        104
    jc89898  
       127 天前
    啥也不是
    lesismal
        105
    lesismal  
       127 天前
    @zzhaolei
    @XyIsMy

    一是: orm 本身是二次加工生成代码,不是直接 sql 语句不直观。日志 level 的方式打印出来的在复杂 orm 使用过程中、可能随着输入参数不一样可能做不到线上所有情况的 sql 范式覆盖,而且 orm 还有一些隐式的东西不是程序员自己设定的,比如一些 orm 库处理数据库的 null 值还是什么来着, 需要额外去绕过,具体哪个库哪些坑我记不清了,各位可以自己搜索引擎搜下或者官方 issue list 去看,我因为抵制 orm 所以对 orm 也没深入研究
    二是,这也是更重要的一点,orm 培养了很多程序员的使用习惯、抛弃了 raw sql ,我见过的绝大多数初级中级同行、以及多数所谓的高级开发(至少八年十年以上经验),都是只要 orm 能实现功能就搞上去了,性能神马的都是事后线上出了问题再去搞。比如上面说的日志 level 打印出 sql 来, 但很多人习惯了 orm 开发方式这种思维,即使打印出来也不会去关注 sql 性能相关. 咱国内人口基数大, 中等规模的公司就可能经常遇到数据量较大的量级, 在做这类项目的时候、尤其是 OLAP 涉及大量统计汇总操作的,一个没留神就被 orm 选手搞性能事故了. 跟这类兄弟共事, 为了避免这些, 研发流程上已经做了很多把关, 但仍瑟瑟发抖.
    lesismal
        106
    lesismal  
       127 天前
    @zzhaolei #93 不是回复我的,我之前没看到,可以看下我 #105 的,也可以看些其他帖子,比如 Xargin 的:
    https://github.com/cch123/blog_comment/issues/254
    还有一些其他帖子例如:
    https://juejin.cn/post/7174222105788514334
    https://studygolang.com/articles/23459

    你可以看到一些 orm 、非 orm 优劣的对比包括 gorm 或者 xorm 的一些坑你都可以去搜下看看。单就性能风险这一点上,高阶一点的开发者基本上是达成一致的,orm 自己的泛型导致的性能损失都是小损失,最大的风险就是我在#105 说的,或者 Xargin 文章里讲的。只有没怎么经手过大项目的才会对 orm 这个坑人风险的说法这么不屑一顾

    “我就是好奇,这些人张嘴闭嘴就是 ORM 有不确定性,到底有什么不确定性,自己测试过?还是踩过坑?还是说嘴一张一闭反正我不管,ORM 就是有性能问题?”

    这个观点是你在自己现阶段水平下得出的结论,但当你以后技术进阶了很可能会自己推翻自己这个观点。如果对技术有追求,可以多试试大项目的工作。如果现在的工作项目量级对性能没那么大要求且钱多那就无所谓了,能挣钱就行,技术都是浮云
    ryalu
        107
    ryalu  
       126 天前
    @XyIsMy #91 上家公司的头头与他的思想恰恰相反,因为团队里难免技术参差不齐,很难避免有人写出垃圾 sql 导致 sql 注入的风险,反而会鼓励我们用 orm..... 上手简单,很难写出危险 sql
    XyIsMy
        108
    XyIsMy  
       126 天前
    @lesismal 风险和习惯这个确实是存在的,避免不了。那怕定好流程规范,开发人员也一样会产出风险。这层只能靠不断的灌输思想和从架构上避免风险。

    但也理解你的想法了,想尽可能使用原生 sql ,一个是降低 orm 隐性的 bug (不一定有,但想尽可能避免),另外一层,在出现性能问题时,可以快速定位问题( orm 在定位 sql 这层是办不到的)
    pangdundun996
        109
    pangdundun996  
       126 天前
    在 python 中,官方库就可以返回[{'col1': 1, 'col2': '2', 'col3': true}....] 这种带类型的 json 结果。
    ------
    谁能解释一下啥叫带类型的 json 吗?
    XyIsMy
        110
    XyIsMy  
       126 天前
    @ryalu 哈哈哈哈, 这个没有绝对的对错, 每个团队的习惯和方案都是经过很多业务问题沉淀出来的。下面简单的举例一下,并不严谨和也不全面。
    比如
    1.小型业务,团队头头会更关注,csrf ,xss ,sql 注入等等攻击风险,因为还跑不到数据库性能层
    2.中型业务,团队头头会更关注,csrf ,xss ,sql 注入 + 应用 sql 调优
    3.大型业务,团队头头会更关注,csrf ,xss ,sql 注入 + 应用 sql 调优 + 数据库实例调优 ,数据安全 等等
    qW7bo2FbzbC0
        111
    qW7bo2FbzbC0  
    OP
       126 天前
    另外 sqlx 最新版本也修复了 map[string]interface{}返回 base64 encode 字符的问题
    ukissme
        112
    ukissme  
       126 天前
    其实还好,楼主少见多怪,自己菜
    lambdaq
        113
    lambdaq  
       126 天前
    你们俩打一架

    /t/1056990 现在极其厌恶弱类型语言,弱类型就不适合中国的职场环境
    /t/1057942 被 go 语言的 json.Marshal 恶心到了
    qW7bo2FbzbC0
        114
    qW7bo2FbzbC0  
    OP
       126 天前
    @all mysql driver 还有这个问题,int 正常,但是 string 还是 base64 encode 字符串,json.RawMessage 也不顶用。sqlite3 的驱动是正常的
    qW7bo2FbzbC0
        115
    qW7bo2FbzbC0  
    OP
       126 天前
    @lambdaq 和强弱类型没有关系,驱动底层逻辑问题
    lambdaq
        116
    lambdaq  
       126 天前
    @qW7bo2FbzbC0 如果说关系的话,你举的这个例子,属于强类型语言里禁术。一个 list/array 里最好类型都是整整齐齐统一的。也就是那个帖子里所说的 “现在好了,到处都是字典 + 字符串取值,这个字符串 key 还是尼玛自己定义的,和 idl 都对不上,得自己去代码里面到处翻找,看看到底是哪个接口返回的。”

    所以我觉得你们俩要不打一架。
    baolongqishi
        117
    baolongqishi  
       126 天前
    zmqiang
        118
    zmqiang  
       126 天前
    这个问题遇到过,这个肯定和 go 的 json 库无关,最多怪到数据库上的 driver 上,没有做好数据类型的映射
    debugman66
        119
    debugman66  
       126 天前
    @XyIsMy 有点好奇是什么样的大公司,连个慢 sql 监控的工具都没有,还需要手动去看代码定位
    XyIsMy
        120
    XyIsMy  
       125 天前
    @debugman66 我也没表达大公司需要手动定位慢 sql 的意思啊。这个说法哪来的哦
    mark2025
        121
    mark2025  
       125 天前   ❤️ 1
    @lesismal 普通 CRUD 项目,orm 是顺手,不过也不比手写 sql 快多少。 多表关联业务系统,orm 就是灾难:光是定义实体类就能让你建吐……
    mark2025
        122
    mark2025  
       125 天前
    @pangdundun996 我也想知道啥是“带类型的 json”,难道说的的是 shema 还是 类似 ts 的 interface ?
    a132811
        123
    a132811  
       124 天前
    楼主已经自己确认了答案了:是这个 sql 库本身实现得有问题,跟 go 、json.Marshal 都无关。

    在某种意义上还是 json 的表达能力不足导致的

    ## 不同语言的 json 库对[]byte 的处理
    json  本身无法区分 string 和 `[]byte`,只有 string 。

    1. php 自己本身就没有`[]byte`,甚至连 dict/array 都不区分。直接回避了问题
    2. python  有`[]byte`, 但是不能 json 序列化, 如果是 utf8 就需要用 data.decode('utf-8') 要先转成 str
    3. golang  序列化`[]byte` 时用 base64 将它转成 string, json 反序列时则用相应的类型去关联`[]byte`类型
    4. java , c sharp 跟 golang 处理方式一样的。

    ## golang 对 json 的支持

    json.Marshal 方法:

    1. json.Marshal 本身是使用了内置的 json encoder 默认使用 base64 处理 bytes, eacapHTML 默认为 true
    2. 但是 json.RawMessage 本身带的 json encoder ,不会对 bytes 做任何处理,如果 bytes 不是 json 格式,就会报编码出错

    某些 golang  库实现,可能出于减少值复制,会用 bytes 去表达 string ,但序列化时就只能是 base64   string 了。

    如果接收者不要 base64 string json, 解决办法有:

    1. 自己先定义一个相应 string struct ,再将 bytes struct 转成 string struct ,最后 json  序列化自然就不是 base64 了
    2. 通过反射将 string struct/map 等 转成  string map 再 json. 参考: https://github.com/ahuigo/golib/tree/main/spec/object/convert/objbytes2string_test.go
    lesismal
        124
    lesismal  
       122 天前
    @mark2025 其实我觉得还是 raw sql 简单点,orm 自己就一大套东西要学,然后不同语言不同 orm 框架都可能存在不同的表达、不同的隐式、不同的坑,但 raw sql 同一个数据库通吃,而且 sql 本身的表达能力远远比 orm 优秀。
    mark2025
        125
    mark2025  
       122 天前
    @lesismal 我觉得 SQL 是一个非常优秀的编程语言,要把表达明晰的 SQL 转换成另外一个表达方式(比如 ORM )是很困难的。
    CRUD 以及简单连表操作我用 kenx 的 querybuilder ,复杂查询直接写 raw sql 去执行。
    lesismal
        126
    lesismal  
       122 天前
    @mark2025 对呀, 所以我一直想不明白很多人号称 orm 比 sql 简单是为什么, 因为我一直学不会 orm. 不是看不懂 orm, 而是因为抵制 orm, 所以每次都不想学, 所以学不会. 要避坑 orm 先要学好多东西, 这本身并不比 raw sql 容易
    mark2025
        127
    mark2025  
       122 天前   ❤️ 1
    @lesismal SQL 是带有过程式风格的语言,要和面向对象的 ORM 之间转换,先不说结果如何,遇上复杂连表查询场景开发人员脑细胞就得 s 一大堆来构造这个对象的调用。
    公司一个公积金项目,java 同事先是用 mybatis 来弄,等实体类创建了一百个之后就绝望放弃了。最后改成 hibernate 那种 xml 方式…… xml…… 可 xml 没发做 ide 的自动提示完成啊。结果某个接口有 130 个字段,有次的 bug 就是字段名写错了……
    mocococ
        128
    mocococ  
       122 天前
    其实可以用 其他比较优秀的框架来试试, 比如 goframe, 这个框架的 orm,绝对够用
    qW7bo2FbzbC0
        129
    qW7bo2FbzbC0  
    OP
       120 天前
    mark2025
        130
    mark2025  
       107 天前
    @ryalu 不同阶段问题矛盾不同
    - 普通 CRUD 场景/中小规模阶段,orm 可以避免新手菜鸟挖坑
    - 复杂业务统计场景/大规模阶段,任何小失误都可能会放大成大事故,所以 orm 虽然一方面可以避免菜鸟的低级错误,但另一方面对老鸟也能反向挖坑,所以需要 DBA 来把关(审查 SQL )
    Brau1589
        131
    Brau1589  
       30 天前
    你可以 自定义个结构体然后重写一下 Scan 方法
    func (你的结构体) Scan(value interface{}) error
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1208 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 23:10 · PVG 07:10 · LAX 15:10 · JFK 18:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.