在生成的 Jersey 末尾添加新行 JSON

Add new line at the end of Jersey generated JSON

我有一个基于 Jersey (1.x) 的 REST 服务。它使用 Jackson 2.4.4 生成 JSON 响应。我需要在响应末尾添加换行符(cURL 用户抱怨响应中没有换行符)。我正在使用 Jersey 漂亮打印功能 (SerializationFeature.INDENT_OUTPUT)。

当前: {\n "prop" : "value"\n}

想要: {\n "prop" : "value"\n}\n

  1. 我尝试使用自定义序列化程序。我只需要在根对象的末尾添加 \n 。序列化器是按数据类型定义的,这意味着,如果这样的 class 的实例嵌套在响应中,我将在我的 JSON 中间得到 \n

  2. 我想到了 subclassing com.fasterxml.jackson.core.JsonGenerator.java,覆盖 close() 我要添加 writeRaw('\n'),但这感觉很老套。

  3. 另一个想法是添加 Servlet 过滤器,它将重写 Jersey 过滤器的响应,添加 \n 并将 contentLenght 增加 1。似乎不仅 hacky,而且效率低下。

  4. 我也可以放弃 Jersey 来处理内容的序列化并做 ObjectMapper.writeValue() + "\n",但这对我的代码来说是相当侵入性的(需要更改很多地方)。

那个问题的干净解决方案是什么?

我找到了这些线程解决同样的问题,但其中 none 提供了解决方案:


更新

最后我用 NewlineAddingPrettyPrinter 寻求@arachnid 的解决方案(也将 Jackson 版本升级到 2.6.2)。可悲的是,它不能与 Jaskson 开箱即用,因为 JAX-RS Json provider. Changed PrettyPrinter in ObjectMapper does not get propagated to JsonGenerator (see here 为什么)。为了让它工作,我必须添加 ResponseFilter ,它会添加 ObjectWriterModifier (现在我可以根据输入参数轻松地在漂亮打印和最小化之间切换):

@Provider
public class PrettyPrintFilter extends BaseResponseFilter {

    public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
        ObjectWriterInjector.set(new PrettyPrintToggler(true));
        return response;
    }

    final class PrettyPrintToggler extends ObjectWriterModifier {

        private static final PrettyPrinter NO_PRETTY_PRINT = new MinimalPrettyPrinter();

        private final boolean usePrettyPrint;

        public PrettyPrintToggler(boolean usePrettyPrint) {
            this.usePrettyPrint = usePrettyPrint;
        }

        @Override
        public ObjectWriter modify(EndpointConfigBase<?> endpoint, MultivaluedMap<String, Object> responseHeaders,
                                   Object valueToWrite, ObjectWriter w, JsonGenerator g) throws IOException {
            if (usePrettyPrint) g.setPrettyPrinter(new NewlineAddingPrettyPrinter());
            else g.setPrettyPrinter(NO_PRETTY_PRINT);
            return w;
        }

    }
}

尚未测试,但以下应该有效:

public class MyObjectMapper extends ObjectMapper {

       _defaultPrettyPrinter = com.fasterxml.jackson.core.util.MinimalPrettyPrinter("\n");

    // AND/OR

       @Override
       protected PrettyPrinter _defaultPrettyPrinter() {
            return new com.fasterxml.jackson.core.util.MinimalPrettyPrinter("\n");
       }

}


public class JerseyConfiguration extends ResourceConfig {
    ...
    MyObjectMapper mapper = new MyObjectMapper();
    mapper.enable(SerializationFeature.INDENT_OUTPUT); //enables pretty printing

    // create JsonProvider to provide custom ObjectMapper
    JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
    provider.setMapper(mapper);
    register(provider); //register so that jersey use it
}

不知道这是否是 "cleanest" 解决方案,但感觉比其他解决方案更简单。

应该产生类似

的东西

{\n "root" : "1"\n}\n{\n "root2" : "2"\n}

但是,如果只有一个根元素,似乎不起作用。

创意来自https://gist.github.com/deverton/7743979

实际上,结束(不是子类化)JsonGenerator 不好:

public static final class NewlineAddingJsonFactory extends JsonFactory {
    @Override
    protected JsonGenerator _createGenerator(Writer out, IOContext ctxt) throws IOException {
        return new NewlineAddingJsonGenerator(super._createGenerator(out, ctxt));
    }

    @Override
    protected JsonGenerator _createUTF8Generator(OutputStream out, IOContext ctxt) throws IOException {
        return new NewlineAddingJsonGenerator(super._createUTF8Generator(out, ctxt));
    }
}

public static final class NewlineAddingJsonGenerator extends JsonGenerator {
    private final JsonGenerator underlying;
    private int depth = 0;

    public NewlineAddingJsonGenerator(JsonGenerator underlying) {
        this.underlying = underlying;
    }

    @Override
    public void writeStartObject() throws IOException {
        underlying.writeStartObject();
        ++depth;
    }

    @Override
    public void writeEndObject() throws IOException {
        underlying.writeEndObject();
        if (--depth == 0) {
            underlying.writeRaw('\n');
        }
    }

    // ... and delegate all the other methods of JsonGenerator (CGLIB can hide this if you put in some time)
}


@Test
public void append_newline_after_end_of_json() throws Exception {
    ObjectWriter writer = new ObjectMapper(new NewlineAddingJsonFactory()).writer();
    assertThat(writer.writeValueAsString(ImmutableMap.of()), equalTo("{}\n"));
    assertThat(writer.writeValueAsString(ImmutableMap.of("foo", "bar")), equalTo("{\"foo\":\"bar\"}\n"));
}

Servlet 过滤器也不一定太糟糕,尽管最近 ServletOutputStream 接口更多地涉及到正确拦截。

我发现在早期的 Jackson 版本(比如你的 2.4.4)上通过 PrettyPrinter 这样做有问题,部分原因是需要通过 ObjectWriter 来正确配置它:仅在 Jackson 2.6 中修复。为了完整起见,这是一个有效的 2.5 解决方案:

@Test
public void append_newline_after_end_of_json() throws Exception {
    // Jackson 2.6:
//      ObjectMapper mapper = new ObjectMapper()
//              .setDefaultPrettyPrinter(new NewlineAddingPrettyPrinter())
//              .enable(SerializationFeature.INDENT_OUTPUT);
//      ObjectWriter writer = mapper.writer();

    ObjectMapper mapper = new ObjectMapper();
    ObjectWriter writer = mapper.writer().with(new NewlineAddingPrettyPrinter());
    assertThat(writer.writeValueAsString(ImmutableMap.of()), equalTo("{}\n"));
    assertThat(writer.writeValueAsString(ImmutableMap.of("foo", "bar")),
            equalTo("{\"foo\":\"bar\"}\n"));
}

public static final class NewlineAddingPrettyPrinter
                    extends MinimalPrettyPrinter
                    implements Instantiatable<PrettyPrinter> {
    private int depth = 0;

    @Override
    public void writeStartObject(JsonGenerator jg) throws IOException, JsonGenerationException {
        super.writeStartObject(jg);
        ++depth;
    }

    @Override
    public void writeEndObject(JsonGenerator jg, int nrOfEntries) throws IOException, JsonGenerationException {
        super.writeEndObject(jg, nrOfEntries);
        if (--depth == 0) {
            jg.writeRaw('\n');
        }
    }

    @Override
    public PrettyPrinter createInstance() {
        return new NewlineAddingPrettyPrinter();
    }
}