corningsun
0.05D
V2EX  ›  Java

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

  •  
  •   corningsun · Oct 23, 2018 · 6053 views
    This topic created in 2764 days ago, the information mentioned may be changed or developed.

    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 的一致,有什么好的方式吗?

    Supplement 1  ·  Oct 23, 2018

    搞定,下班 😁

    重写了 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(' ');
        }
    }
    

    谢谢大家的建议了 🙏

    Supplement 2  ·  Apr 11, 2019

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

    https://github.com/corningsun/JavaJsonDumps

    23 replies    2023-09-11 14:24:40 +08:00
    linhua
        1
    linhua  
       Oct 23, 2018
    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  
       Oct 23, 2018
    json.dumps(demo_bean, sort_keys=True, separators=(',', ':'))
    unifier
        3
    unifier  
       Oct 23, 2018
    楼上两位没好好看题呀,楼主说 Python 部分不能改了,只能想办法在 Java 里加了
    misaka19000
        4
    misaka19000  
       Oct 23, 2018
    改算法,JSON 类型的数据不应该因为空格就导致数据的 hash 结果不一致,更好的办法是根据 key 和 value 的值来计算 hash 的值
    whileFalse
        5
    whileFalse  
       Oct 23, 2018 via iPhone
    竟然不是排序 key 之后根据 key 和 value 自己写 hash …
    xmt328
        6
    xmt328  
       Oct 23, 2018
    这个设计好有问题啊,环境依赖的这么严重,谁设计的不怕被打么
    corningsun
        7
    corningsun  
    OP
       Oct 23, 2018
    @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  
       Oct 23, 2018
    @corningsun #7 既然是这样那么比较挫的办法就是把所有的 ',' replace 为', ' 了
    corningsun
        9
    corningsun  
    OP
       Oct 23, 2018
    @misaka19000
    现在就是这么干的,但是有个字段是富文本,很容易把别的内容覆盖掉,所以来找更好的方法。
    PulpFunction
        10
    PulpFunction  
       Oct 23, 2018
    上正则
    或者在值后面加个 logo
    比如{"id":1@#*,"name":"demoName@#*","values":[1@#*,2@#*,3@#*,4@#*]}
    接着再用 1 楼的方案
    PulpFunction
        11
    PulpFunction  
       Oct 23, 2018
    再富的文本也不能富过 logo 吧
    嫌短再加长点
    PulpFunction
        12
    PulpFunction  
       Oct 23, 2018
    我觉得 3 个###就行
    PulpFunction
        13
    PulpFunction  
       Oct 23, 2018
    还有细节问题
    PulpFunction
        14
    PulpFunction  
       Oct 23, 2018
    {"id":***1###,"name":"***demoName###","values":***[1###,2###,3###,4]}
    PulpFunction
        15
    PulpFunction  
       Oct 23, 2018
    采纳记得发送感谢,每一个都发送啊,币不多了
    kkkkkrua
        16
    kkkkkrua  
       Oct 23, 2018
    String value = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(demoApplication);
    System.out.println(value.replace("\r\n","").replace("{ ","{")); //最后的首{可以用正则匹配下中间的空格
    kkkkkrua
        17
    kkkkkrua  
       Oct 23, 2018
    思路是先美化输出,然后替换掉换行,再替换开头的{中间的空格
    //美化输出的
    {
    "name" : "张三",
    "age" : 18
    }
    //替换后的
    {"name" : "张三", "age" : 18}
    woodensail
        18
    woodensail  
       Oct 23, 2018
    不能拿到原始的 requestbody 直接进行 hash 吗?
    kkkkkrua
        19
    kkkkkrua  
       Oct 23, 2018
    忘记说了,包是 com.fasterxml.jackson.databind.ObjectMapper;不知道 fastjson 有没有美化方法
    woodensail
        20
    woodensail  
       Oct 23, 2018
    或者可以这么干。请求就带俩参数,一个是 md5 一个是 data。data 就是 json 字符串。服务端拿到后直接对字符串进行 md5 校验,校验通过了对 data 进行解析得到真实参数。
    corningsun
        21
    corningsun  
    OP
       Oct 23, 2018
    @woodensail 服务端代码改不了了。
    @kkkkkrua
    fastjson 也有 pretty 方法,但是只是增加了 换行,没有加空格。
    objectMapper 的 pretty 方法,在冒号的 两边都加了空格,只去除换行还不够。另外处理 list 数组时,也不一致。
    corningsun
        22
    corningsun  
    OP
       Oct 23, 2018
    终于找到方法了,具体看附言内容,谢谢大家了。
    Sum0l
        23
    Sum0l  
       Sep 11, 2023
    学习了,还得是该源码最可靠 哈哈
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   972 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 1902ms · UTC 20:07 · PVG 04:07 · LAX 13:07 · JFK 16:07
    ♥ Do have faith in what you're doing.