Skip to content

Commit f57bc1a

Browse files
committed
MappingJackson(2)JsonView allows subclasses to access the ObjectMapper and to override content writing
Issue: SPR-7619
1 parent af8e625 commit f57bc1a

File tree

2 files changed

+126
-79
lines changed

2 files changed

+126
-79
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/view/json/MappingJackson2JsonView.java

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.web.servlet.view.json;
1818

1919
import java.io.ByteArrayOutputStream;
20+
import java.io.IOException;
2021
import java.io.OutputStream;
2122
import java.util.Collections;
2223
import java.util.HashMap;
@@ -27,7 +28,6 @@
2728

2829
import com.fasterxml.jackson.core.JsonEncoding;
2930
import com.fasterxml.jackson.core.JsonGenerator;
30-
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
3131
import com.fasterxml.jackson.databind.ObjectMapper;
3232
import com.fasterxml.jackson.databind.SerializationFeature;
3333

@@ -48,13 +48,15 @@
4848
* @author Jeremy Grelle
4949
* @author Arjen Poutsma
5050
* @author Rossen Stoyanchev
51+
* @author Juergen Hoeller
5152
* @since 3.1.2
5253
* @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
5354
*/
5455
public class MappingJackson2JsonView extends AbstractView {
5556

5657
/**
57-
* Default content type. Overridable as bean property.
58+
* Default content type: "application/json".
59+
* Overridable through {@link #setContentType}.
5860
*/
5961
public static final String DEFAULT_CONTENT_TYPE = "application/json";
6062

@@ -75,8 +77,9 @@ public class MappingJackson2JsonView extends AbstractView {
7577

7678
private boolean updateContentLength = false;
7779

80+
7881
/**
79-
* Construct a new {@code JacksonJsonView}, setting the content type to {@code application/json}.
82+
* Construct a new {@code MappingJackson2JsonView}, setting the content type to {@code application/json}.
8083
*/
8184
public MappingJackson2JsonView() {
8285
setContentType(DEFAULT_CONTENT_TYPE);
@@ -85,38 +88,44 @@ public MappingJackson2JsonView() {
8588

8689

8790
/**
88-
* Sets the {@code ObjectMapper} for this view.
89-
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used.
90-
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control
91-
* of the JSON serialization process. For example, an extended {@code SerializerFactory}
92-
* can be configured that provides custom serializers for specific types. The other option
93-
* for refining the serialization process is to use Jackson's provided annotations on the
94-
* types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
91+
* Set the {@code ObjectMapper} for this view.
92+
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} will be used.
93+
* <p>Setting a custom-configured {@code ObjectMapper} is one way to take further control of
94+
* the JSON serialization process. The other option is to use Jackson's provided annotations
95+
* on the types to be serialized, in which case a custom-configured ObjectMapper is unnecessary.
9596
*/
9697
public void setObjectMapper(ObjectMapper objectMapper) {
9798
Assert.notNull(objectMapper, "'objectMapper' must not be null");
9899
this.objectMapper = objectMapper;
99100
configurePrettyPrint();
100101
}
101102

102-
private void configurePrettyPrint() {
103-
if (this.prettyPrint != null) {
104-
this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint);
105-
}
103+
/**
104+
* Return the {@code ObjectMapper} for this view.
105+
*/
106+
public final ObjectMapper getObjectMapper() {
107+
return this.objectMapper;
106108
}
107109

108110
/**
109-
* Set the {@code JsonEncoding} for this converter.
111+
* Set the {@code JsonEncoding} for this view.
110112
* By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used.
111113
*/
112114
public void setEncoding(JsonEncoding encoding) {
113115
Assert.notNull(encoding, "'encoding' must not be null");
114116
this.encoding = encoding;
115117
}
116118

119+
/**
120+
* Return the {@code JsonEncoding} for this view.
121+
*/
122+
public final JsonEncoding getEncoding() {
123+
return this.encoding;
124+
}
125+
117126
/**
118127
* Indicates whether the JSON output by this view should be prefixed with <tt>"{} && "</tt>.
119-
* Default is false.
128+
* Default is {@code false}.
120129
* <p>Prefixing the JSON string in this manner is used to help prevent JSON Hijacking.
121130
* The prefix renders the string syntactically invalid as a script so that it cannot be hijacked.
122131
* This prefix does not affect the evaluation of JSON, but if JSON validation is performed
@@ -127,12 +136,11 @@ public void setPrefixJson(boolean prefixJson) {
127136
}
128137

129138
/**
130-
* Whether to use the {@link DefaultPrettyPrinter} when writing JSON.
139+
* Whether to use the default pretty printer when writing JSON.
131140
* This is a shortcut for setting up an {@code ObjectMapper} as follows:
132141
* <pre>
133142
* ObjectMapper mapper = new ObjectMapper();
134143
* mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
135-
* converter.setObjectMapper(mapper);
136144
* </pre>
137145
* <p>The default value is {@code false}.
138146
*/
@@ -141,6 +149,12 @@ public void setPrettyPrint(boolean prettyPrint) {
141149
configurePrettyPrint();
142150
}
143151

152+
private void configurePrettyPrint() {
153+
if (this.prettyPrint != null) {
154+
this.objectMapper.configure(SerializationFeature.INDENT_OUTPUT, this.prettyPrint);
155+
}
156+
}
157+
144158
/**
145159
* Set the attribute in the model that should be rendered by this view.
146160
* When set, all other model attributes will be ignored.
@@ -160,7 +174,7 @@ public void setModelKeys(Set<String> modelKeys) {
160174
/**
161175
* Return the attributes in the model that should be rendered by this view.
162176
*/
163-
public Set<String> getModelKeys() {
177+
public final Set<String> getModelKeys() {
164178
return this.modelKeys;
165179
}
166180

@@ -179,7 +193,7 @@ public void setRenderedAttributes(Set<String> renderedAttributes) {
179193
* @deprecated use {@link #getModelKeys()} instead
180194
*/
181195
@Deprecated
182-
public Set<String> getRenderedAttributes() {
196+
public final Set<String> getRenderedAttributes() {
183197
return this.modelKeys;
184198
}
185199

@@ -212,6 +226,7 @@ public void setUpdateContentLength(boolean updateContentLength) {
212226
this.updateContentLength = updateContentLength;
213227
}
214228

229+
215230
@Override
216231
protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
217232
setResponseContentType(request, response);
@@ -227,34 +242,21 @@ protected void prepareResponse(HttpServletRequest request, HttpServletResponse r
227242
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
228243
HttpServletResponse response) throws Exception {
229244

230-
OutputStream stream = this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream();
231-
245+
OutputStream stream = (this.updateContentLength ? createTemporaryOutputStream() : response.getOutputStream());
232246
Object value = filterModel(model);
233-
JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding);
234-
235-
// A workaround for JsonGenerators not applying serialization features
236-
// https://github.com/FasterXML/jackson-databind/issues/12
237-
if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
238-
generator.useDefaultPrettyPrinter();
239-
}
240-
241-
if (this.prefixJson) {
242-
generator.writeRaw("{} && ");
243-
}
244-
this.objectMapper.writeValue(generator, value);
245-
247+
writeContent(stream, value, this.prefixJson);
246248
if (this.updateContentLength) {
247249
writeToResponse(response, (ByteArrayOutputStream) stream);
248250
}
249251
}
250252

251253
/**
252-
* Filters out undesired attributes from the given model.
254+
* Filter out undesired attributes from the given model.
253255
* The return value can be either another {@link Map} or a single value object.
254256
* <p>The default implementation removes {@link BindingResult} instances and entries
255257
* not included in the {@link #setRenderedAttributes renderedAttributes} property.
256258
* @param model the model, as passed on to {@link #renderMergedOutputModel}
257-
* @return the object to be rendered
259+
* @return the value to be rendered
258260
*/
259261
protected Object filterModel(Map<String, Object> model) {
260262
Map<String, Object> result = new HashMap<String, Object>(model.size());
@@ -267,4 +269,27 @@ protected Object filterModel(Map<String, Object> model) {
267269
return (this.extractValueFromSingleKeyModel && result.size() == 1 ? result.values().iterator().next() : result);
268270
}
269271

272+
/**
273+
* Write the actual JSON content to the stream.
274+
* @param stream the output stream to use
275+
* @param value the value to be rendered, as returned from {@link #filterModel}
276+
* @param prefixJson whether the JSON output by this view should be prefixed
277+
* with <tt>"{} && "</tt> (as indicated through {@link #setPrefixJson})
278+
* @throws IOException if writing failed
279+
*/
280+
protected void writeContent(OutputStream stream, Object value, boolean prefixJson) throws IOException {
281+
JsonGenerator generator = this.objectMapper.getJsonFactory().createJsonGenerator(stream, this.encoding);
282+
283+
// A workaround for JsonGenerators not applying serialization features
284+
// https://github.com/FasterXML/jackson-databind/issues/12
285+
if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
286+
generator.useDefaultPrettyPrinter();
287+
}
288+
289+
if (prefixJson) {
290+
generator.writeRaw("{} && ");
291+
}
292+
this.objectMapper.writeValue(generator, value);
293+
}
294+
270295
}

0 commit comments

Comments
 (0)