【问题标题】:How can I configure Jackson to serialize Longs as Strings, but not Integers or doubles?如何配置 Jackson 将 Longs 序列化为字符串,而不是整数或双精度数?
【发布时间】:2017-11-03 21:44:20
【问题描述】:

我们之前使用的框架(云端点)会自动将 java.lang.Long 序列化为 JSON 字符串(同时将整数和双精度数保留为 JSON 数字)。我们正在迁移以使用更标准的 Jackson 序列化框架。

为了向后兼容,我们需要 Jackson 能够将 Long 序列化为字符串,同时默认将整数和双精度数保留为 JSON 数字。我没有看到杰克逊有任何功能可以做到这一点。这可能吗?

【问题讨论】:

    标签: java json serialization jackson


    【解决方案1】:

    Long.classlong.class 使用自定义序列化程序:

    public static class LongSerializer extends JsonSerializer<Long> {
        @Override
        public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(value.toString());
        }
    }
    
    public static class LongDeserializer extends JsonDeserializer<Long> {
        @Override
        public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            return p.getValueAsLong();
        }
    }
    

    您需要将它们注册到您的映射器:

    public static void main(String[] args) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
    
        SimpleModule module = new SimpleModule();
        module.addSerializer(long.class, new LongSerializer());
        module.addSerializer(Long.class, new LongSerializer());
        module.addDeserializer(long.class, new LongDeserializer());
        module.addDeserializer(Long.class, new LongDeserializer());
        objectMapper.registerModule(module);
    
        Test test = new Test();
        String json = objectMapper.writeValueAsString(test);
        System.out.println(json);
    }
    

    【讨论】:

    • 你确定deserialize 会在值用引号括起来的情况下工作吗?
    • 是的,我查过了。
    • 好的,我将尝试这种自定义序列化程序方法。我有点担心这种潜在的破坏性招摇和其他不知道自定义序列化程序的技术,但我会试一试。
    【解决方案2】:

    选项 1. 自定义序列化程序

    @teppic 答案应该适用于大多数情况,但是

    1. 您不需要自定义反序列化程序。默认的反序列化器可以处理 String => long

    2. 可以使用com.fasterxml.jackson.databind.ser.std.ToStringSerializer序列化器

      module.addSerializer(Long.class, ToStringSerializer.instance);
      module.addSerializer(long.class, ToStringSerializer.instance);
      
    3. 您会遇到边缘情况,因为自定义序列化器方法实际上无法处理另一个自定义序列化器使用 this JsonGenerator method 的情况

      (JsonGenerator) g.writeNumber((long)value);
      

      这里最重要的边缘情况是long[].class

      static class MyBean {
          long val = 1;        // works fine "1"
          Long val2 = 2L;      // works fine "2"
          long[] vals = {1,2}; // doesn't work [1,2]
      }
      

    其实你可以

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(long.class, ToStringSerializer.instance);
        module.addSerializer(long[].class, new JsonSerializer<long[]>(){
            @Override
            public final void serialize(
                    long[] value, JsonGenerator g, SerializerProvider provider)
                    throws IOException {
    
                g.writeStartArray();
                for (int i = 0, len = value.length; i < len; ++i) 
                    g.writeString(String.valueOf(value[i]));
    
                g.writeEndArray();
            }
        });
    
        // register every other serializers that use 
        // (JsonGenerator) g.writeNumber((long)value); 
        // like AtomicLong, if you use it in your code
    
        mapper.registerModule(module);
    
        MyBean bean = new MyBean();
        String json = mapper.writeValueAsString(bean);
        MyBean myBean2 = mapper.readValue(json, MyBean.class);
    
        System.out.println(json);
        System.out.println(myBean2);
        System.out.println(mapper.writeValueAsString(myBean2));
    }
    

    选项 2. 自定义 JsonFactory 和 JsonGenerator

    但话又说回来,这对你来说足够了吗,你确定没有人会尝试使用jsonGenerator.writeNumber(long) 还是你有其他的类,实际上默认使用它?因此,100% 可行的解决方案是在 ObjectMapper 上注册自定义 JsonFactory,并使用 2 个重写方法 _createGenerator_createUTF8Generator

    JsonFactory factory =  new JsonFactory(){
        @Override
        protected JsonGenerator _createGenerator(Writer out, IOContext ctxt)
                throws IOException {
            WriterBasedJsonGenerator gen = new WriterBasedJsonGenerator(ctxt,
                    _generatorFeatures, _objectCodec, out){
                @Override
                public void writeNumber(long l) throws IOException {
                    writeString(String.valueOf(l));
                }
            };
            return _internalSuperSetup(gen);
        }
    
        @Override
        protected JsonGenerator _createUTF8Generator(OutputStream out, IOContext ctxt)
                throws IOException {
            UTF8JsonGenerator gen = new UTF8JsonGenerator(ctxt,
                    _generatorFeatures, _objectCodec, out){
                @Override
                public void writeNumber(long l) throws IOException {
                    writeString(String.valueOf(l));
                }
            };
            return _internalSuperSetup(gen);
        }
    
        // reuse code from _createUTF8Generator and _createGenerator super methods
        private JsonGenerator _internalSuperSetup(JsonGenerator gen){
            if (_characterEscapes != null) gen.setCharacterEscapes(_characterEscapes);
            SerializableString rootSep = _rootValueSeparator;
            if (rootSep != DefaultPrettyPrinter.DEFAULT_ROOT_VALUE_SEPARATOR) 
                gen.setRootValueSeparator(rootSep);
            return gen;
        }
    };
    ObjectMapper mapper = new ObjectMapper(factory);
    

    【讨论】:

    • 谢谢,这里有一些有用的信息。我相信我们确实需要一个自定义的反序列化器,因为如果你不能把你在响应中收到的相同的 json 放回,那将是一个非常糟糕的 API。我们可能会结合您的建议和以前的解决方案。感谢您提及long[].class 的特殊情况。我们只使用List&lt;Long&gt;,但为了完整起见,我们应该处理这种情况。
    • @CharlesCapps 当我说你不需要自定义反序列化器时,我是说默认的已经可以处理字符串 => 默认情况下的长转换。资料来源:github.com/FasterXML/jackson-databind/blob/master/src/main/java/…,它可以处理更多案件然后@teppic LongDeserializer。您想要滚动自己的反序列化器的唯一情况是,如果您想通过 json 中的 longs 作为数字的异常。而且它不仅是long[] 的特殊情况,它还会为AtomicLong 以及更多...
    • 啊酷@varren,我确认正在使用默认反序列化器将字符串反序列化为 Long。我本可以发誓我遇到了一个错误,但我认为这是一个专门用于处理 GeoJSON 的库的问题。在这种情况下,我们似乎只需要为Long.classlong.classlong[].class 注册ToStringSerializer。是的,我预计我们不会使用很多自定义(反)序列化器,如果我们这样做,自定义(反)序列化器将负责正确处理 long。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-19
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多