在Jersey末尾添加新行生成JSON

时间:2021-06-26 19:33:16

I have a Jersey (1.x) based REST service. It uses Jackson 2.4.4 to generate JSON responses. I need to add a newline character at the end of response (cURL users complain that there's no new line in responses). I am using Jersey pretty-print feature (SerializationFeature.INDENT_OUTPUT).

我有一个基于球衣(1.x)的休息服务。它使用Jackson 2.4.4生成JSON响应。我需要在响应末尾添加一个换行字符(cURL用户抱怨响应中没有换行)。我正在使用Jersey的漂亮打印特性(serizationfeature.indent_output)。

current: {\n "prop" : "value"\n}

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

wanted: {\n "prop" : "value"\n}\n

通缉:{\n“道具”:“价值”\n

  1. I tried using a custom serializer. I need to add \n only at the end of the root object. Serializer is defined per data type, which means, if an instance of such class is nested in a response, I will get \n in the middle of my JSON.

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

  2. I thought of subclassing com.fasterxml.jackson.core.JsonGenerator.java, overriding close() where i'd add writeRaw('\n'), but that feels very hacky.

    我想到了子类化com.fasterxml.jackson.core.JsonGenerator。在我添加writeRaw('\n')的地方,java,重写close(),但这感觉很粗糙。

  3. Another idea would be to add Servlet filter which would re-write the response from Jersey Filter, adding the \n and incrementing the contentLenght by 1. Seems not only hacky, but also inefficient.

    另一个想法是添加Servlet过滤器,它将重新编写来自Jersey过滤器的响应,添加\n,并将contentLenght增加1。似乎不仅陈腐,而且低效。

  4. I could also give up Jersey taking care of serializing the content and do ObjectMapper.writeValue() + "\n", but this is quite intrusive to my code (need to change many places).

    我还可以放弃Jersey,负责序列化内容和ObjectMapper.writeValue() +“\n”,但是这对我的代码来说是很烦人的(需要更改很多地方)。

What is the clean solution for that problem?

解决这个问题的干净方法是什么?

I have found these threads for the same problem, but none of them provides solution:

我已经找到了同样问题的这些线程,但是没有一个提供解决方案:


Update

更新

Finally I went for @arachnid's solution with NewlineAddingPrettyPrinter (also bumper Jackson version to 2.6.2). Sadly, it does not work out of the box with Jaskson as JAX-RS Json provider. Changed PrettyPrinter in ObjectMapper does not get propagated to JsonGenerator (see here why). To make it work, I had to add ResponseFilter which adds ObjectWriterModifier (now I can easily toggle between pretty-print and minimal, based on input param ):

最后,我使用了NewlineAddingPrettyPrinter(同样是Jackson版本的2.6.2)来解决@arachnid的解决方案。遗憾的是,使用Jaskson作为JAX-RS Json提供程序并不是一件容易的事。ObjectMapper中更改的PrettyPrinter不会被传播到JsonGenerator(请参见这里的原因)。为了让它工作,我必须添加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;
        }

    }
}

2 个解决方案

#1


1  

Actually, wrapping up (not subclassing) JsonGenerator isn't too bad:

实际上,封装(不是子类化)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"));
}

A servlet filter isn't necessarily too bad either, although recently the ServletOutputStream interface has been more involved to intercept properly.

servlet过滤器也不一定很糟糕,尽管最近servlet outputstream接口更需要正确地拦截。

I found doing this via PrettyPrinter problematic on earlier Jackson versions (such as your 2.4.4), in part because of the need to go through an ObjectWriter to configure it properly: only fixed in Jackson 2.6. For completeness, this is a working 2.5 solution:

我发现在早期的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();
    }
}

#2


0  

Not yet tested but the following should work:

还没有经过测试,但以下方法应该有效:

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
}

Do not know if this is the "cleanest" solution but it feels less hacky than the others.

不知道这是否是“最干净”的解决方案,但它感觉没有其他方案那么陈腐。

Should produce something like

应当会产生类似

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

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

But it seems that does not work if there is only one root element.

但如果只有一个根元素,这似乎就行不通了。

Idea is from https://gist.github.com/deverton/7743979

来自https://gist.github.com/deverton/7743979的想法是

#1


1  

Actually, wrapping up (not subclassing) JsonGenerator isn't too bad:

实际上,封装(不是子类化)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"));
}

A servlet filter isn't necessarily too bad either, although recently the ServletOutputStream interface has been more involved to intercept properly.

servlet过滤器也不一定很糟糕,尽管最近servlet outputstream接口更需要正确地拦截。

I found doing this via PrettyPrinter problematic on earlier Jackson versions (such as your 2.4.4), in part because of the need to go through an ObjectWriter to configure it properly: only fixed in Jackson 2.6. For completeness, this is a working 2.5 solution:

我发现在早期的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();
    }
}

#2


0  

Not yet tested but the following should work:

还没有经过测试,但以下方法应该有效:

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
}

Do not know if this is the "cleanest" solution but it feels less hacky than the others.

不知道这是否是“最干净”的解决方案,但它感觉没有其他方案那么陈腐。

Should produce something like

应当会产生类似

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

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

But it seems that does not work if there is only one root element.

但如果只有一个根元素,这似乎就行不通了。

Idea is from https://gist.github.com/deverton/7743979

来自https://gist.github.com/deverton/7743979的想法是