【问题标题】:Jackson ObjectMapper - specify serialization order of object propertiesJackson ObjectMapper - 指定对象属性的序列化顺序
【发布时间】:2015-02-19 01:51:23
【问题描述】:

我正在实现一个 RESTful Web 服务,用户必须在请求中发送一个签名的验证令牌,以便我可以确保请求没有被中间人篡改。我目前的实现如下。

验证令牌是一个 VerifData 对象,序列化为字符串,然后进行散列和加密。

class VerifData {
    int prop1;
    int prop2;
}

在我的服务中,我将要序列化的数据放入 VerifData 的实例中,然后使用 Jackson ObjectMapper 对其进行序列化,并与验证令牌一起传递给验证引擎。

VerfiData verifData = new VerifData(12345, 67890);
ObjectMapper mapper = new ObjectMapper();
String verifCodeGenerated = mapper.writeValueAsString(verifData);

但似乎每次启动应用容器时,ObjectMapper映射成字符串的属性顺序都会发生变化。

例如:有一次

{"prop1":12345,"prop2":67890}

还有一次

{"prop2":67890,"prop1":12345}

因此,如果客户端已将 VerifData 实例序列化为第一个字符串,即使它是正确的,它仍有 50% 的机会失败。

有没有办法解决这个问题?我可以通过 ObjectMapper 指定要映射的属性的顺序(如升序)吗?或者有没有其他方法可以最好地实现这个验证步骤。客户端和服务器实现都是由我开发的。我使用 Java Security API 进行签名和验证。

【问题讨论】:

    标签: java json serialization jackson


    【解决方案1】:

    注释很有用,但在任何地方都可能会很痛苦。您可以将整个 ObjectMapper 配置为以这种方式使用

    杰克逊的当前版本:

    objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)

    杰克逊的旧版本:

    objectMapper.configure(SerializationConfig.Feature.SORT_PROPERTIES_ALPHABETICALLY, true);

    【讨论】:

    • 这并不完全正确(或者至少在所有情况下都不起作用)——请参阅 stackoverflow.com/a/46267506/2089674 以获取有效的完整示例。
    • @user2089674 - 您的示例仅适用于 Map 键。 OP 请求对象字段的解决方案。
    【解决方案2】:

    来自Jackson Annotations documentation

    // ensure that "id" and "name" are output before other properties
    @JsonPropertyOrder({ "id", "name" })
    
    // order any properties that don't have explicit setting using alphabetic order
    @JsonPropertyOrder(alphabetic=true)
    

    【讨论】:

    • 来自链接“所有codehaus服务已终止”
    • 如下所述,如果您的目标是在任何地方应用排序,而不是在序列化特定对象时,请参阅stackoverflow.com/a/46267506/2089674
    【解决方案3】:

    需要以下2 ObjectMapper 配置:

    ObjectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)

    ObjectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)

    定义用于 POJO 字段的属性序列化顺序
    注意是否适用于java.util.Map 序列化!


    ObjectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)

    ObjectMapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)

    确定java.util.Map条目是否在序列化之前首先按键排序


    Spring Boot 配置示例(yaml):

    spring:
      jackson:
        mapper:
          SORT_PROPERTIES_ALPHABETICALLY: true
        serialization:
          ORDER_MAP_ENTRIES_BY_KEYS: true
    

    【讨论】:

      【解决方案4】:

      在 Spring Boot 中,您可以通过将以下内容添加到您的 Application 入口点类来全局添加此行为:

        @Bean
        public Jackson2ObjectMapperBuilder objectMapperBuilder() {
      
          Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
          builder.featuresToEnable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
      
          return builder;
        }
      

      【讨论】:

        【解决方案5】:

        在 Spring Boot 中有一种更简单的方法是指定一个属性(例如在 application.properties 中:

        spring.jackson.mapper.sort_properties_alphabetically=true
        

        【讨论】:

          【解决方案6】:

          在您今天可能正在使用的 Jackson 2.x 中,使用:

          ObjectMapper mapper = new ObjectMapper();
          mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
          

          如果你在意颜值,也可以考虑SerializationFeature.INDENT_OUTPUT

          请注意,您必须序列化 MapsObjects 才能正确排序。例如,如果您序列化 JsonNode(来自 readTree),则不会正确缩进。

          示例

          import com.fasterxml.jackson.databind.*;
          
          ObjectMapper mapper = new ObjectMapper();
          mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
          mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
          
          String input = "{\"hello\": {\"cruel\" : \"world\"} }";
          Object pojo = mapper.readValue(input, Object.class);
          System.out.println(mapper.writeValueAsString(pojo));
          

          结果:

          {
            "hello" : {
              "cruel" : "world"
            }
          }
          

          【讨论】:

          • 只有一个地图条目。因此我看不到它的顺序。
          【解决方案7】:

          来自邓肯麦格雷戈的回答: 最好像这样使用它:

          objectMapper.configure(SerializationConfig.Feature.SORT_PROPERTIES_ALPHABETICALLY, true);
          

          因为 MapperFeature 是用于 XML 并且带有不需要的 jackson-databind...

          【讨论】:

            【解决方案8】:

            我意识到这是一个旧线程,但由于我正在寻找或回答并登陆这里,因此其他人可能会方便使用一些额外的信息。
            我目前使用的@JsonProperty 注释(jackson-annotations-2.11.2)除了“值”参数外,还接受一个“索引”(数字)参数,用于指定序列化期间字段的顺序。

            【讨论】:

              【解决方案9】:

              我今天发现了另一种方法,以防字母不是您想要的排序顺序。事实证明,如果其余字段未注释,则在写入时在字段上添加 @JsonProperty 注释将其放在最后。我发现当我想指定一个不符合 java 命名约定的属性名称时。

              通过添加索引属性,您可以定义顺序。最低的索引放在第一位。

              @JsonProperty(index=20)
              String prop1;
              
              @JsonProperty(index=10)
              String prop2;
              

              将呈现:

              {"prop2": "valueProp2", "prop1": "valueProp1"}
              

              【讨论】:

                【解决方案10】:

                不使用标志参数:

                objectMapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
                

                【讨论】:

                  【解决方案11】:

                  你可以使用 mix-in 并根据需要指定属性的顺序:

                  import com.fasterxml.jackson.annotation.JsonPropertyOrder;
                  import com.fasterxml.jackson.databind.ObjectMapper;
                  
                  import org.springframework.context.annotation.Bean;
                  import org.springframework.stereotype.Component;
                  
                  @Component
                  public final class ObjectMapperUtils {
                  
                      private static final ObjectMapper MAPPER = new ObjectMapper();
                  
                      static {
                          MAPPER.addMixIn(Object.class, IdFirst.class);
                      }
                  
                      @Bean
                      public ObjectMapper objectMapper() {
                          return MAPPER;
                      }
                  
                      @JsonPropertyOrder({"id", "...", "..."})
                      private abstract static class IdFirst {}
                  
                  }
                  

                  【讨论】:

                    【解决方案12】:

                    正如@Gary Rowe 提到的,我们可以使用 Jackson2ObjectMapperBuilder 对属性进行全局排序。 但是,要使其正常工作,您的类路径中必须有 Jackson2ObjectMapperBuilder。它不是 Jackson 库的一部分。

                    根据this documentation,spring-web 依赖项有 Jackson2ObjectMapperBuilder 文件,应该在你的类路径中。

                    @Bean
                      public Jackson2ObjectMapperBuilder objectMapperBuilder() {
                    
                        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
                        builder.featuresToEnable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
                    
                        return builder;
                      }
                    

                    您可以参考this了解其他可能的解决方案

                    【讨论】:

                      猜你喜欢
                      • 2020-08-23
                      • 1970-01-01
                      • 2013-10-16
                      • 2021-10-05
                      • 1970-01-01
                      • 2016-06-20
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多