V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
winiex
V2EX  ›  程序员

可能是另一场圣战:后端返回的 JSON 的值是只要 String 类型呢,还是各种类型都包括呢?

  •  
  •   winiex · 2018-05-19 15:21:21 +08:00 · 19544 次点击
    这是一个创建于 2382 天前的主题,其中的信息可能已经有所发展或是发生改变。

    工作中和不同的客户端开发者合作过,有的要求返回的 JSON 统一只包含 String 类型:

    { a: "THYM", b: "107", c: "false" }

    而有的则要求数据要表达自己的类型:

    { a: "THYM", b: 107, c: false }

    我个人是支持第二种写法的,因为不用再写一堆转 String 和转回类型的代码。大家团队都选择何种方式呢?又是出于何种原因与理由呢?

    161 条回复    2018-05-22 22:11:10 +08:00
    1  2  
    iyangyuan
        101
    iyangyuan  
       2018-05-20 05:50:05 +08:00 via iPhone
    让我想起了自己刚接触编程的时候,以为编程就是字符串处理
    zwh2698
        102
    zwh2698  
       2018-05-20 06:50:06 +08:00 via Android
    没有什么好战的,肯定是第二种,标准就是实践后的总结
    msg7086
        103
    msg7086  
       2018-05-20 07:17:00 +08:00
    偷懒一时爽,_________。

    因为要解决一些开发人员水平不过关而导致的技术问题,特地去引入沉重的技术债?
    等过几年的就会被自己挖下的坑给坑死。

    当然你也可以跳槽了事,让其他留下来的倒霉员工为你擦腚。
    yhvictor
        104
    yhvictor  
       2018-05-20 08:17:31 +08:00 via iPhone   ❤️ 2
    选 1 的组织力量开发个 2 转 1 的库吧,这样这个世界就清净了。
    vjnjc
        105
    vjnjc  
       2018-05-20 08:55:52 +08:00 via Android
    也倾向于第二种,但感觉第一种更不容易崩溃。
    wizardoz
        106
    wizardoz  
       2018-05-20 10:39:39 +08:00
    既然用 JSON 作为数据交换格式,那么跟两头语言是不是兼容有半毛钱关系?不管对方是啥语言,我都不管,我只要把 JSON 转成自己能够使用的对象。
    chocotan
        107
    chocotan  
       2018-05-20 10:44:45 +08:00
    第二种
    liuhuansir
        108
    liuhuansir  
       2018-05-20 10:49:18 +08:00
    @wizardoz 我也是这么认为的,既然 json 格式标准带了类型,大家都遵守这个标准就行了,把自己的事情做好,屁事都没有
    zhouquanbest
        109
    zhouquanbest  
       2018-05-20 11:23:06 +08:00 via Android
    一切都按标准来 https://www.json.org
    毋庸置疑第二种
    Golevka
        110
    Golevka  
       2018-05-20 11:31:46 +08:00
    要么"严格地发,宽容地收",要么“严格地发,严格地收“。
    想避免应用由于 JSON 的类型问题而崩溃就宽容地收就好了。
    所以要求要求返回的 JSON 统一只包含 String 类型是几个意思?能再奇葩点儿不?
    sagaxu
        111
    sagaxu  
       2018-05-20 11:50:39 +08:00
    @hyyou2010 “这里有个误会,我是说后台有可能送的不是 null,而是 nil 或其他。”,这锅后台可不背,你能找个常见的后台 JSON 库用 nil 表示 null 的?
    gnaggnoyil
        112
    gnaggnoyil  
       2018-05-20 12:05:12 +08:00   ❤️ 2
    @huclengyue 这和静态动态类型有什么关系?你要搞清楚 JSON 规范的类型和语言的类型之间的区别,任何一个图灵完全的语言都可以做到接收一段 JSON 字符串并且判断给定的某个字段的值是不是某个类型的.所以 JSON 规范中的类型更多地是规范本身钦点了特殊值(true,false,null 什么的)的表示方式,从而避免占用 string 所能表示的状态,好让 string 的状态 trival 地一一映射到其所欲代表地语义.典型例子就是"null"用来表示一个由 n,u,l,l 四个字符组成的字符串,而 null 则表示空这个状态.考虑到这个世界上还有不少人的名字(或者昵称)就是"null",把 null 用"null"代替的行为可以说是对目标用户非常不负责.

    唯一可能的例外就是 JSON 的 number 本身有精度和表示范围的限制所以在表示超出限制的数的时候不得不 fallback 到 string 上.但这是唯一的例外了.特别地,但凡是有人准备用字符串来代替 null,true 和 false 的语义的,见一个打死一个.
    scriptB0y
        113
    scriptB0y  
       2018-05-20 12:14:20 +08:00
    @GreenVine @iwtbauh 原来是这样,那是我孤陋寡闻了,学习了。
    zvving
        114
    zvving  
       2018-05-20 13:58:27 +08:00   ❤️ 1
    肯定第二种。

    JSON 标准支持 bool, number, string 就应该合理的使用。使用第一种方案的的确解决了一些问题,但不是合理的解决方案。(设计合理的解决方案要在遵循标准基础上)

    简单罗列一些思路:
    输出端(服务器)要符合标准,死磕定义
    - 合理定义类型:bool, number, string
    - 改用 bool 的用 bool,改用 enum 的用 int
    - 规范处理 null, "", 0, [], {}
    - 常见错误:非数值定义,如手机号、ID,不应该用 int,应该用 string


    接收段(客户端)要做的:
    - 合理处理类型见自动转换:int <-> float <-> String,包括自定义的:如 iso8601string <-> Date
    - 合理处理空、空值:
    - 避免 float 精度丢失,数值移除
    - 如果服务器输出 json 符合约定,后续业务变化客户端 crash,这锅该客户端背


    这些不想清楚,一股脑儿用 string,low !
    Crabbbbb
        115
    Crabbbbb  
       2018-05-20 15:02:22 +08:00
    你们这算好了,最近我接的一个 s*接口,返回是类似下面的

    "{ a: \"THYM\", b: \"107\", c: \"false\" }"

    你没看错,头尾那两个引号真的也给我返回了
    image72
        116
    image72  
       2018-05-20 16:15:03 +08:00
    JSON-schema 包治百病
    wizardforcel
        117
    wizardforcel  
       2018-05-20 16:23:36 +08:00
    @luoyou1014 啥叫“类型错误”,类型不应该也是接口的一部分吗??还变来变去??

    我把 b 写成 "abcd",parseInt 照样闪退你信不信??
    wizardforcel
        118
    wizardforcel  
       2018-05-20 16:27:00 +08:00
    字符串解决不了任何问题,反而会隐藏问题。

    就算你规定了字符串,字符串的格式呢??客户端期待一个整数,可是接口规定它是个字符串,那我后可不可以传个只包含字母的字符串?

    你当然也可以规定“表示数值的字符串”,那干嘛不直接规定成数值??逗我??
    des
        119
    des  
       2018-05-20 16:37:19 +08:00 via Android
    说第二种省事的,你怕是没见过 select *之后结果直接吐给客户端的。
    话说要是能都是统一的,没有歧义第一种我也能接受。

    还有 null 和""是不一样的,以及 enum 我更倾向用直面量,而不是 int
    h1367500190
        120
    h1367500190  
       2018-05-20 17:17:23 +08:00
    @Eoss 你太低估用第一种的人的想象力了

    我还见过用 for 循环和字符串拼接 JSON 的。。
    h1367500190
        121
    h1367500190  
       2018-05-20 17:30:12 +08:00
    @des 我司有个组就是 select * 吐给客户端的,我都是写适配器取出需要的字段并强制转换类型。

    我觉得可选字段返回 null 完全没有问题,null 表示没有或不存在,""是实体字符串只不过内容为空而已。

    我也赞成而且极力劝说我司后端 enum 用字面量而不是 int,不然返回 1、2、3 之类的数字都不知道是什么鬼,有文档得一直查文档,没文档就呵呵
    vicunart
        122
    vicunart  
       2018-05-20 17:41:43 +08:00
    哈哈,你这算个啥...
    我这后端特么返的 xml 格式,还特么还用来接微信小程序呢。
    sammo
        123
    sammo  
       2018-05-20 19:29:41 +08:00
    后端传送给前端,肯定要保证网络传送的安全性,肯定要统一。那么其他都不用管,返回值是同一种类型就可以。
    第一种 是同一种类型 ( String 类型 )
    第二种不是同一种类型 ( 多种类型 ) -- 肯定不选
    如果有第三种 是同一种类型 ( int 类型 ) ,那么第一种和第三种还可以看情况考虑一下,当然 为了统一 在同一种类型的基础上 最后还是 string 优先
    prasanta
        124
    prasanta  
       2018-05-20 21:25:49 +08:00 via Android
    一派胡言,json 就要有 json 的样子
    hlwjia
        125
    hlwjia  
       2018-05-20 22:50:15 +08:00 via iPhone
    这种问题没啥好争的,从理论上讲是第二种完胜。

    但实际情况(技术人员的素质,legacy code 等等)会导致没办法做第二种。

    如果是新起项目选第二种是没错的,如果团队的其他人也是这么认为的话

    或者你可以在面试的时候就问面试官这些问题


    我自己一个人做项目,每次我都提取一些精华放到下一个项目中。
    hyyou2010
        126
    hyyou2010  
       2018-05-20 23:27:16 +08:00
    今天又思考了一下,进一步肯定了 1,主要思路在于分层。

    后端 1、2、3 客户端 1、2、3
    应用层 1、2、3 应用层 1、2、3
    接口层 接口层
    + +
    +-----------接口定义---------+


    我说说 1 的好处:
    1、接口层是 json 的一个最简子集,也即只有字符串,那么可以肯定地说,由于这种简化,使得前后端的接口层都会非常简单稳固。
    2、接口层可以由单独的底层小组来开发,完成之后把这个接口层提供给前后端多个业务小组使用,业务小组工作在应用层,应用层才知道 json 的全集,包括 null 和对象等。
    3、业务小组极其可能经常修改数据类型,注意,此刻要崩也在业务层崩,不在接口层崩,业务小组调试查 bug 的时候不用找接口层小组。
    4、无论应用层怎样改动,无需改动接口层。接口层只有 1 个,应用层是多端多应用。

    如上,我认为 1 很好。
    hyyou2010
        127
    hyyou2010  
       2018-05-20 23:29:28 +08:00
    v2ex 这个怎么会帮我删除空格???我的图没法看了。

    如下重画:

    后端 1、2、3 .......................客户端 1、2、3
    应用层 1、2、3 ....................应用层 1、2、3
    接口层.................................. 接口层
    +-----------接口定义----------------+
    pexcn
        128
    pexcn  
       2018-05-20 23:30:01 +08:00
    我上家公司的老项目用 Y/N 表示 true/false (
    hyyou2010
        129
    hyyou2010  
       2018-05-20 23:35:39 +08:00
    软件设计里面有个思路,就是把变和不变的部分独立为不同的模块,层也一样,分隔变和不变的层,我们把永远可以不变的层提取出来作为底层,把经常变动更改的层作为上层。
    imn1
        130
    imn1  
       2018-05-21 00:24:55 +08:00
    补充一个伦理的观点:
    第一种全部用 String,不存在不符合标准 /规范的问题,能遵守 json 协议就是符合了
    究竟用单类型,还是多类型,其实说到底是看项目的

    例如:
    我以前一个程序,要判断某股票是否 ST,实际上就是 bool,因为我只关心它的涨跌停是否限制到 5%
    但给出数据的那一方会这样理解么,肯定要写清 ST 的细节状态,例如面临退市之类,要作出必要警告

    对同一个 json,里面的类型,0/1 究竟是 bool/int/string,只要项目需求不同,各自理解就有差异
    所以,并不能说第一种就是要批判的,尤其当 json 的用户很多,众口难调时

    类似的「圣战」其实上世纪就有了——
    csv 中的数字究竟该不该用双引号
    or
    csv 中不含分隔符的字符串该不该用双引号
    night98
        131
    night98  
       2018-05-21 00:34:53 +08:00 via Android
    第一种没那么烂吧。

    比如现在有个 status 字段表示状态,默认 0 是不锁定,1 为锁定,按照正常来说应该选择 boolean 类型,但是如果要加第三种类型~正在处理,这种的话就很好改了,但是如果是 boolean 类型就很麻烦。
    binux
        132
    binux  
       2018-05-21 01:34:50 +08:00   ❤️ 1
    @sammo 后端传给前端的,在网络传送中的明明都是 bytes,怎么就不统一了。你怎么反序列化这个 bytes 和网络传输有个蛋关系。就算你“第一种 是同一种类型 ( String 类型 )” 你还可能在 String 转 Int,转 Bool 的时候挂掉。
    iiji86
        133
    iiji86  
       2018-05-21 08:19:51 +08:00 via iPhone
    @night98 但是楼主讨论的是对于布尔值应该转成字符串的"true"、"false"返回,还是直接返回 true、false 的问题,并不是你说的接口设计应不应该使用布尔值的问题啊
    startar
        134
    startar  
       2018-05-21 09:04:48 +08:00 via Android
    补充一个非用字符串不可的 case,传递 64 位整数
    lockelee
        135
    lockelee  
       2018-05-21 09:12:16 +08:00
    要是外包项目他爱怎么搞怎么搞,能交付别找你麻烦就行。
    要是产品类的,长期维护的项目,根本不用圣战,肯定第二种啊=、=
    koalli
        136
    koalli  
       2018-05-21 09:44:07 +08:00
    我和 php 服务端对接的时候无数次遇到商量好的给数组结果给过来一个 key-value 表的情况...
    在我们的客户端里,这两种不同类型的结构在遍历、取值上由于性能差异需要调用不同的方法,约定好格式其实本质上是为了出问题的时候方便追责(甩锅)。
    楼上有个兄 dei 说得对,客户端永远都是不相信服务端的。因为你不知道服务端线上部署后会不会出幺蛾子返回莫名其妙的数据,比如某台服务器出错了其他通过这台服务器获取的数据返回给客户端就出了问题,甚至还会有第三方的服务器返回的结果有问题,实际情况往往更复杂。
    另外防御性的代码写多了分支也就多了,测试的路径覆盖不够充分的话就容易埋雷出了问题难以调试,部分语言里 if-else 分支多了甚至会影响性能,所以客户端服务端双方需要商量一个统一个规则,最好当然是使用 protobuf 之类的工具来做解析。
    skyfore
        137
    skyfore  
       2018-05-21 09:58:08 +08:00
    前后端分离 + 严格的定义标准 + 清晰的 API
    不就完事了吗?非要这样来搞什么兼容
    USNaWen
        138
    USNaWen  
       2018-05-21 10:11:51 +08:00
    肯定第二种啊,要是 json 里有数组和嵌套的 obj 第一个怎么办?再解析一次?
    严格类型后端检查长期只有好处没有坏处。
    ragnaroks
        139
    ragnaroks  
       2018-05-21 10:23:03 +08:00
    //因为接口方技术低劣,这里需要手动对接收的数据进行预处理,严重影响了开发效率
    Code Here

    等 code review 阶段老大发飙
    Lanke0
        140
    Lanke0  
       2018-05-21 10:28:09 +08:00
    看情况吧,一般是第一种。不是你想怎么玩就怎么玩,而是大家要沟通好
    jerray
        141
    jerray  
       2018-05-21 11:21:46 +08:00
    第二种。如果前端觉得不爽,那就再招个觉得可以的前端。
    zonyitoo
        142
    zonyitoo  
       2018-05-21 11:26:21 +08:00
    第一种的后端肯定用的不是强类型的语言来写的,不然怎么可能写得出来第一种。
    比如 PHP,Node
    用强类型语言写的话,后端自己判断的时候总不可能都是字符串判断和转换,麻烦
    zonyitoo
        143
    zonyitoo  
       2018-05-21 11:30:02 +08:00
    实在想不出怎么可能会直接返回第二种,可能正如上面所说,直接 select *出来,不处理直接转成 json 返回去吧。即使是高级一点点的带 DAO 的,也会变成正确的类型。只能说,实在了懒,对质量完全没有追求
    htfy96
        144
    htfy96  
       2018-05-21 11:31:20 +08:00 via Android
    API 的兼容性应该通过明确的 versioning 来解决。旧版本客户端用的应该是 /api/v1,在服务端需要变化 api,且旧版本 API 已经投入使用时,应该完全新开一个 /api/v2 同时保留 v1 接口不变,而不是通过所谓隐式转换这种 trick 瞎猫碰死耗子兼容性刚好碰对
    sunmonster
        145
    sunmonster  
       2018-05-21 11:37:36 +08:00
    @Eoss 牛逼了,编程学到你这种份上也是厉害,不能 if(val === '1') 来判断?
    h1367500190
        146
    h1367500190  
       2018-05-21 11:57:14 +08:00
    @Eoss 看下 #145 楼,这就是为什么说你低估了用第一种方法的人的想象力的原因
    h1367500190
        147
    h1367500190  
       2018-05-21 12:02:41 +08:00
    @Eoss 总之程序能跑就行,类型、语义、可读性...不存在的
    Eoss
        148
    Eoss  
       2018-05-21 12:43:06 +08:00 via iPhone
    @h1367500190
    没收到他的消息提醒,反而收到你的。不知道是我屏蔽了他还是他被降权了。可能人家搬砖还不够多或者不是搬砖的。
    随意他吧。反正和我对接我就要求第二种。最近和 java 对接,项目开始前开了个会明确用第二种。
    luoyou1014
        149
    luoyou1014  
       2018-05-21 13:40:52 +08:00
    @wizardforcel 不要总是用反问句,这样会显的你攻击性很强。
    luoway
        150
    luoway  
       2018-05-21 13:58:48 +08:00
    确实是一场“圣战”了。
    问题的核心是兼容别人的问题,还是坚持写好自己的代码?
    对于客户端而言,确实不能相信接口严格按约定格式返回。最好使用前校验一遍。
    所以虽然第二种很漂亮,但是业务使用中建议第一种,更健壮。
    zhea55
        151
    zhea55  
       2018-05-21 15:28:21 +08:00
    !( https://imgur.com/a/sTZpGOl)

    [jsbin 计算结果]( https://jsbin.com/rosivaqeru/1/edit?html,js,output)



    自己写的方法,转化后端 恶心 数据。
    zhea55
        152
    zhea55  
       2018-05-21 15:37:09 +08:00
    ![]( )


    输入对象可以 nest 嵌套其他数据类型。
    HangoX
        153
    HangoX  
       2018-05-21 17:01:29 +08:00 via Android
    @luoyou1014 不会漏。。解析都在统一的地方,你只需要填写 bean 就可以了,蹦是不可能的。
    littlebaozi
        154
    littlebaozi  
       2018-05-21 17:20:14 +08:00
    ===去判断的时候就得先转类型才行了
    cuzfinal
        155
    cuzfinal  
       2018-05-21 17:22:54 +08:00
    有什么好战的,要不然 json 为什么去定义 true、null 这些类型
    zhjits
        156
    zhjits  
       2018-05-21 17:23:37 +08:00
    非常奇怪为什么有人在生产代码上解析(即使是来自自家后端的)外部数据不做异常处理。
    ConradG
        157
    ConradG  
       2018-05-21 17:26:25 +08:00
    第一种叫 JSON ……?!(惊
    可能经历的少,我还真没见过第一种写法的。
    wizardforcel
        158
    wizardforcel  
       2018-05-21 22:23:07 +08:00
    @des 后端的 ORM 会使用反射自动装填对象,它不可能将原本是数值的字段转成字符串,JSON 模板也不会这样做。所以在后端做这件事情实际上是要花费额外人力的。
    rotoava
        159
    rotoava  
       2018-05-22 12:44:42 +08:00
    数字类型最好全用字符串传递,要不功能都实现不了,谈不上规范。
    collinswang
        160
    collinswang  
       2018-05-22 13:53:23 +08:00
    这个我觉得是从数据库层传导上来的。
    MYSQL 数据库设计的时候,后端表示 true/false 时一般都是用 0,1 来表示,不会用 bool,因为需求一改,要增加一种状态,那非常麻烦,整个底层都得动。
    而 null,虽然 MYSQL 数据库有 null,但是也一般设计成默认值为 empty string。
    这些就从底层一直传导到前端,变成方法一 的输出效果了
    hyyou2010
        161
    hyyou2010  
       2018-05-22 22:11:10 +08:00
    @collinswang 的这个推理比较可信。正如我前面提及的分层,如果最底的接口层只允许字符串,易变和多义的 null+bool+object 让应用层自行处理,那么 1 就是很好的方案。
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1028 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 40ms · UTC 20:12 · PVG 04:12 · LAX 12:12 · JFK 15:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.