37
votes

I am storing objects in my database as JSON strings. I want to make a REST service that exposes these strings. When I write my methods however, the strings I get back have their quotes escaped. For example, I have included a method that returns a String,

@RequestMapping(value = "test", method = RequestMethod.GET)
public @ResponseBody
String getTest() {
    return "{\"a\":1, \"b\":\"foo\"}";
}

but when I call this method in the browser I get a back "{\"a\":1, \"b\":\"foo\"}" when what I really want to happen is {"a": 1, "b": "foo"}. I think "String" as the return type is likely the problem, but what else can I do? A wrapper class does the same thing:

{
  "value" : "{\"a\":1, \"b\":\"foo\"}"
}

I could serialize it and then return the object, but that seems a bit ridiculous. Here is a possibly the relevant portion of my configuration file:

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    super.configureMessageConverters(converters);
    converters.add(mappingJacksonHttpMessageConverter());
}

@Bean
MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter() {
    MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
    mappingJacksonHttpMessageConverter.setObjectMapper(objectMapper);
    mappingJacksonHttpMessageConverter.setPrettyPrint(true);
    return mappingJacksonHttpMessageConverter;
}

Thanks

EDIT: as was suggested below, it seems the string is being double encoded. Commenting out the 2 classes in my configuration fixes this issue. However, I still have other places where I want to return Objects and would like to keep those running through that common serializing bean that I know where to configure. So I see my options as: a) Do all the serializing myself. All methods return Strings, and those that are already JSON return themselves, and those that are objects all return JSONUtil.toJson(object). I don't love this approach, but I know it will work. b) Use a wrapper class that looks kind of like:

public static class Wrapper {

    @JsonRawValue
    private final String value;
}

This leads to an awkward "value" at the front though that has no real meaning.

Basically what I want is @JsonRawValue, but to have it work on RequestMapping methods instead of properties. Thoughts? Opinions? Other suggestions?

9

9 Answers

47
votes

This works with Jackson 2 (at least):

@Controller
public class YourController {

    @RequestMapping(..)
    public @ResponseBody Json get() {
        return new Json("{ \"attr\" : \"value\" }");
    }
}

class Json {

    private final String value;

    public Json(String value) {
        this.value = value;
    }

    @JsonValue
    @JsonRawValue
    public String value() {
        return value;
    }
}

Not particularly pretty but works. I only wish Spring supported this:

@RequestMapping(..)
public @JsonRawValue @ResponseBody String get() {
    // ...
}
28
votes

I guess what you want is producing a response with content-type application/json. In your case, when you have the json-data as a raw string, do the following:

In your controller add produces="application/json" to your @RequestMapping attribute:

@RequestMapping(value = "test", method = RequestMethod.GET, produces="application/json")
public @ResponseBody
String getTest() {
    return "{\"a\":1, \"b\":\"foo\"}";
}

Then you have to configure the StringHttpMessageConverter to accept the application/json media-type.

With Java-config:

@Override
public void configureMessageConverters(
        List<HttpMessageConverter<?>> converters) {
    StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(
            Charset.forName("UTF-8"));
    stringConverter.setSupportedMediaTypes(Arrays.asList( //
            MediaType.TEXT_PLAIN, //
            MediaType.TEXT_HTML, //
            MediaType.APPLICATION_JSON));
    converters.add(stringConverter);
}

With XML-config:

<bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="messageConverters">
        <array>
            <bean class = "org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes" value="application/json; charset=UTF-8" />
            </bean>
        </array>
    </property>
</bean>
11
votes

I used this:

@RequestMapping(..)
@ResponseBody
public JsonNode myGetRequest(){
...
//rawJsonString is the raw Json that we want to proxy back to the client
return objectMapper.readTree(rawJsonString);
}

And the Jackson converter knew how to transform the JsonNode into plain Json.

5
votes

If you want to convert JSON String to JSON object in your browser, keep string convertor before Jackson convertor.
Follow this link for complete example. It works with custom converter configuration plus spring validation.

It Works

converters.add(stringConverter());
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);

It Doesn't

converters.add(mappingJackson2HttpMessageConverter());
converters.add(stringConverter());
super.configureMessageConverters(converters);
4
votes

In my case, I wanted the response type to be determined by a request parameter, so had to specify the content type in the code, e.g.:

@RequestMapping("/myurl")
public void radiusSearch(@RequestParam responseType, HttpServletResponse response) throws IOException {
    String jsonResponse = makeSomeJson();
    response.setContentType(responseType);
    try {
        response.getOutputStream().write(jsonResponse.getBytes());
    } finally {
        response.getOutputStream().close();
    }
}
2
votes

Today we had the same issue and solved it with multiple converters. Now every String will treated as a string and every other Object will get serialised by Jackson. This allows to serialise manually (by returning String) or automatically (by returning something else) in Spring controllers.

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(stringConverter());
    converters.add(mappingJackson2HttpMessageConverter());
    super.configureMessageConverters(converters);
}

@Bean
public StringHttpMessageConverter stringConverter() {
    final StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(UTF_8);
    stringConverter.setSupportedMediaTypes(Arrays.asList(
            MediaType.TEXT_PLAIN,
            MediaType.TEXT_HTML,
            MediaType.APPLICATION_JSON));
    return stringConverter;
}

@Bean
public GenericHttpMessageConverter<Object> mappingJackson2HttpMessageConverter() {
    final ObjectMapper objectMapper = objectMapperBuilder().build();
    final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper);
    return converter;
}
1
votes

The \" means the character " is being escaped, which is standard. If it's being printed like that, you're probably double-serializing the object.

0
votes

I know this is an old question, but I was just dealing with the opposite problem myself (I was returning a String and WANTED it to get converted to JSON). In your case, it sounds like you simply want to have your String treated as a plain string and not have any sort of JSON conversion done on it as you already have JSON.

So in your case you don't want to use the MappingJacksonHttpMessageConverter (or the MappingJackson2HttpMessageConverter if you're now using Jackson2). You want no conversions done at all, and that converter converts Java objects to/from JSON. So instead you should just use the plain StringHttpMessageConverter. You can do that by changing your setup method like this:

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new StringHttpMessageConverter());
}

This converter is applicable to */* types (the document is incorrect which says text/*, I found out the hard way in the debugger). So whether your content type is application/json or not, either way Spring won't mess with your Strings if you use this converter.

0
votes

The solution to your problem is, this works perfectly without changing any configurations

import com.fasterxml.jackson.databind.JsonNode;
import com.github.fge.jackson.JsonLoader;

JsonNode getTest() {
    return JsonLoader.fromString("{\"a\":1, \"b\":\"foo\"}");
}