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

Java JSON 序列化如何匹配 Python json.dumps() 结果

  •  
  •   corningsun · 2018-10-23 14:12:17 +08:00 · 5435 次点击
    这是一个创建于 2217 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Java 项目对接 Python 服务端接口时,需要做参数 md5 校验

    Python 中处理参数是通过 json.dumps() 方式先拿到请求 json 再计算 md5 值的

    但是 Python 和 Java 序列化的结果不一致,导致 md5 验证无法通过

    Python 代码示例

    #!/usr/bin/env python3
    # coding: utf-8
    
    from json import dumps
    
    if __name__ == '__main__':
    
        demo_bean = {
            "id": 1,
            "name": "demoName",
            "values": [1, 2, 3, 4]
        }
        demo_json = dumps(demo_bean, sort_keys=True).encode('utf-8')
        print(demo_json)
    
    // 输出结果:
    // b'{"id": 1, "name": "demoName", "values": [1, 2, 3, 4]}'
    
    

    Java 我用的是 FastJson

    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import com.google.common.collect.Lists;
    import lombok.Builder;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.Test;
    
    import java.util.List;
    
    @Slf4j
    public class FastJsonTest {
    
        @Test
        public void testClean() throws Exception {
            DemoBean demoBean = DemoBean.builder()
                    .id(1)
                    .name("demoName")
                    .values(Lists.newArrayList(1, 2, 3, 4))
                    .build();
    
            String demoJson = JSON.toJSONString(demoBean, SerializerFeature.SortField);
    
            log.debug("demoJson={}", demoJson);
        }
    }
    
    @Data
    @Builder
    class DemoBean {
        private Integer id;
        private String name;
        private List<Integer> values;
    }
    
    // 输出
    // demoJson={"id":1,"name":"demoName","values":[1,2,3,4]}
    

    我比较了下两种方式的输出,主要是 Python 序列化结果多了一些 “空格”。

    python: {"id": 1, "name": "demoName", "values": [1, 2, 3, 4]}
    java  : {"id":1,"name":"demoName","values":[1,2,3,4]}
    

    目前 Python 服务端代码不能变更,只能使 Java 的 json 对象序列化结果和 Python 的一致,有什么好的方式吗?

    第 1 条附言  ·  2018-10-23 18:37:51 +08:00

    搞定,下班 😁

    重写了 jackson 的 MinimalPrettyPrinter 类,具体看下面:

    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import com.fasterxml.jackson.core.JsonGenerationException;
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.core.util.MinimalPrettyPrinter;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    ...
    
    @Slf4j
    public class FastJsonTest {
    
        @Test
        public void testClean() throws Exception {
            DemoBean demoBean = DemoBean.builder()
                    .id(1)
                    .name("demoName")
                    .values(Lists.newArrayList(1, 2, 3, 4))
                    .build();
            String demoJson = JSON.toJSONString(demoBean, SerializerFeature.SortField);
            log.debug("demoJson={}", demoJson);
            // {"id":1,"name":"demoName","values":[1,2,3,4]}
    
            String demoJson2 = new ObjectMapper().writer(new MyPrettyPrinter()).writeValueAsString(demoBean);
            log.debug("demoJson2={}", demoJson2);
            // {"id": 1, "name": "demoName", "values": [1, 2, 3, 4]}
        }
    }
    
    @Data
    @Builder
    class DemoBean {
        private Integer id;
        private String name;
        private List<Integer> values;
    }
    
    class MyPrettyPrinter extends MinimalPrettyPrinter {
    
        @Override
        public void writeObjectFieldValueSeparator(JsonGenerator jg) throws IOException {
            jg.writeRaw(':');
            jg.writeRaw(' ');
        }
    
        @Override
        public void writeObjectEntrySeparator(JsonGenerator jg) throws IOException, JsonGenerationException {
            jg.writeRaw(',');
            jg.writeRaw(' ');
        }
    
        @Override
        public void writeArrayValueSeparator(JsonGenerator jg) throws IOException, JsonGenerationException {
            jg.writeRaw(',');
            jg.writeRaw(' ');
        }
    }
    

    谢谢大家的建议了 🙏

    第 2 条附言  ·  2019-04-11 15:08:03 +08:00

    原来的总结还忽略了中文的情况,趁现在可以摸鱼,汇总了下,希望大家用不上

    https://github.com/corningsun/JavaJsonDumps

    23 条回复    2023-09-11 14:24:40 +08:00
    linhua
        1
    linhua  
       2018-10-23 14:30:25 +08:00
    https://stackoverflow.com/questions/16311562/python-json-without-whitespaces
    https://docs.python.org/3/library/json.html#json.dump
    If specified, separators should be an (item_separator, key_separator) tuple. The default is (', ', ': ') if indent is None and (',', ': ') otherwise. To get the most compact JSON representation, you should specify (',', ':') to eliminate whitespace.

    手动将', '和': '替换成',',':'
    myyou
        2
    myyou  
       2018-10-23 14:31:27 +08:00
    json.dumps(demo_bean, sort_keys=True, separators=(',', ':'))
    zacharyjia
        3
    zacharyjia  
       2018-10-23 14:36:42 +08:00
    楼上两位没好好看题呀,楼主说 Python 部分不能改了,只能想办法在 Java 里加了
    misaka19000
        4
    misaka19000  
       2018-10-23 14:40:28 +08:00
    改算法,JSON 类型的数据不应该因为空格就导致数据的 hash 结果不一致,更好的办法是根据 key 和 value 的值来计算 hash 的值
    whileFalse
        5
    whileFalse  
       2018-10-23 14:49:32 +08:00 via iPhone
    竟然不是排序 key 之后根据 key 和 value 自己写 hash …
    xmt328
        6
    xmt328  
       2018-10-23 16:10:03 +08:00
    这个设计好有问题啊,环境依赖的这么严重,谁设计的不怕被打么
    corningsun
        7
    corningsun  
    OP
       2018-10-23 16:10:23 +08:00
    @misaka19000 @whileFalse

    是的,但是 Python 服务端现状就是这个样子了,没法让对方改了。

    已经把 FastJson 源码看了一遍了,并没有找到设置“空格”的地方。。😢

    ```java
    package com.alibaba.fastjson.serializer;

    public class FieldSerializer implements Comparable<FieldSerializer> {

    private final String double_quoted_fieldPrefix;
    private String single_quoted_fieldPrefix;

    public FieldSerializer(Class<?> beanType, FieldInfo fieldInfo){
    ...

    this.double_quoted_fieldPrefix = '"' + fieldInfo.name + "\":";
    ...
    }

    public void writePrefix(JSONSerializer serializer) throws IOException {
    SerializeWriter out = serializer.out;

    if (out.quoteFieldNames) {
    if (out.useSingleQuotes) {
    if (single_quoted_fieldPrefix == null) {
    single_quoted_fieldPrefix = '\'' + fieldInfo.name + "\':";
    }
    out.write(single_quoted_fieldPrefix);
    } else {
    out.write(double_quoted_fieldPrefix);
    }
    } else {
    if (un_quoted_fieldPrefix == null) {
    this.un_quoted_fieldPrefix = fieldInfo.name + ":";
    }
    out.write(un_quoted_fieldPrefix);
    }
    }
    ```

    misaka19000
        8
    misaka19000  
       2018-10-23 16:15:04 +08:00
    @corningsun #7 既然是这样那么比较挫的办法就是把所有的 ',' replace 为', ' 了
    corningsun
        9
    corningsun  
    OP
       2018-10-23 16:20:46 +08:00
    @misaka19000
    现在就是这么干的,但是有个字段是富文本,很容易把别的内容覆盖掉,所以来找更好的方法。
    PulpFunction
        10
    PulpFunction  
       2018-10-23 16:44:56 +08:00
    上正则
    或者在值后面加个 logo
    比如{"id":1@#*,"name":"demoName@#*","values":[1@#*,2@#*,3@#*,4@#*]}
    接着再用 1 楼的方案
    PulpFunction
        11
    PulpFunction  
       2018-10-23 16:46:13 +08:00
    再富的文本也不能富过 logo 吧
    嫌短再加长点
    PulpFunction
        12
    PulpFunction  
       2018-10-23 16:47:02 +08:00
    我觉得 3 个###就行
    PulpFunction
        13
    PulpFunction  
       2018-10-23 16:48:42 +08:00
    还有细节问题
    PulpFunction
        14
    PulpFunction  
       2018-10-23 16:54:11 +08:00
    {"id":***1###,"name":"***demoName###","values":***[1###,2###,3###,4]}
    PulpFunction
        15
    PulpFunction  
       2018-10-23 16:55:12 +08:00
    采纳记得发送感谢,每一个都发送啊,币不多了
    kkkkkrua
        16
    kkkkkrua  
       2018-10-23 17:07:32 +08:00
    String value = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(demoApplication);
    System.out.println(value.replace("\r\n","").replace("{ ","{")); //最后的首{可以用正则匹配下中间的空格
    kkkkkrua
        17
    kkkkkrua  
       2018-10-23 17:08:52 +08:00
    思路是先美化输出,然后替换掉换行,再替换开头的{中间的空格
    //美化输出的
    {
    "name" : "张三",
    "age" : 18
    }
    //替换后的
    {"name" : "张三", "age" : 18}
    woodensail
        18
    woodensail  
       2018-10-23 17:09:56 +08:00
    不能拿到原始的 requestbody 直接进行 hash 吗?
    kkkkkrua
        19
    kkkkkrua  
       2018-10-23 17:10:36 +08:00
    忘记说了,包是 com.fasterxml.jackson.databind.ObjectMapper;不知道 fastjson 有没有美化方法
    woodensail
        20
    woodensail  
       2018-10-23 17:23:54 +08:00
    或者可以这么干。请求就带俩参数,一个是 md5 一个是 data。data 就是 json 字符串。服务端拿到后直接对字符串进行 md5 校验,校验通过了对 data 进行解析得到真实参数。
    corningsun
        21
    corningsun  
    OP
       2018-10-23 17:57:51 +08:00
    @woodensail 服务端代码改不了了。
    @kkkkkrua
    fastjson 也有 pretty 方法,但是只是增加了 换行,没有加空格。
    objectMapper 的 pretty 方法,在冒号的 两边都加了空格,只去除换行还不够。另外处理 list 数组时,也不一致。
    corningsun
        22
    corningsun  
    OP
       2018-10-23 18:39:25 +08:00
    终于找到方法了,具体看附言内容,谢谢大家了。
    Sum0l
        23
    Sum0l  
       2023-09-11 14:24:40 +08:00
    学习了,还得是该源码最可靠 哈哈
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1284 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 23:51 · PVG 07:51 · LAX 15:51 · JFK 18:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.