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

bot*_*que 6 json jersey jackson

我有一个基于 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只需要在根对象的末尾添加。序列化程序是按数据类型定义的,这意味着,如果此类类的实例嵌套在响应中,我将\n处于 JSON 的中间。

  2. 我想到了子类化com.fasterxml.jackson.core.JsonGenerator.java,覆盖close() 我要添加的地方writeRaw('\n'),但这感觉很hacky。

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

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

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

我发现这些线程解决了同样的问题,但没有一个提供解决方案:


更新

最后,我选择了@arachnid 的解决方案NewlineAddingPrettyPrinter(也将 Jackson 版本提高到 2.6.2)。遗憾的是,Jaskson 作为JAX-RS Json 提供程序并不能开箱即用。改变PrettyPrinterObjectMapper没有得到传播到JsonGenerator(见这里为什么)。为了使它工作,我必须添加ResponseFilterwhich added 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;
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

ara*_*nid 5

事实上,结束了(不继承)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"));
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)