Skip to content

Loading…

[#323] Implemented single, customisable global ObjectMapper #225

Closed
wants to merge 4 commits into from

2 participants

@jroper
Play Framework member

This fixes issue 323, as talked about here:

https://groups.google.com/forum/?fromgroups#!searchin/play-framework/objectmapper/play-framework/9CkHWRaRs_g/GsAp5Wg5NOoJ

I've implemented tests for this, and run them, and they pass. It supports still using play.libs.Json with no application running, and so is backwards compatible with old test code that does use Json without a running application, but if one is running, it will load a per application configured ObjectMapper, which may be provided using the ObjectMapperProvider interface and json.objectMapperProvider configuration parameter.

@pk11
Play Framework member

We do not think it's a good idea to introduce this as a plugin, since this is a core functionality (and also it addresses only the Java API). We probably should discuss the best way forward on the mailing list. Thanks!

@pk11 pk11 closed this
@Wilhansen Wilhansen added a commit to Wilhansen/Play20 that referenced this pull request
@erwan erwan [#225] Accept the PATCH method in the routes file de372c8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
84 framework/src/play/src/main/java/play/libs/Json.java
@@ -5,6 +5,9 @@
import org.codehaus.jackson.*;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.node.*;
+import play.Application;
+import play.Play;
+import play.Plugin;
/**
* Helper functions to handle JsonNode values.
@@ -18,7 +21,7 @@
*/
public static JsonNode toJson(final Object data) {
try {
- return new ObjectMapper().valueToTree(data);
+ return mapper().valueToTree(data);
} catch(Exception e) {
throw new RuntimeException(e);
}
@@ -32,7 +35,7 @@ public static JsonNode toJson(final Object data) {
*/
public static <A> A fromJson(JsonNode json, Class<A> clazz) {
try {
- return new ObjectMapper().treeToValue(json, clazz);
+ return mapper().treeToValue(json, clazz);
} catch(Exception e) {
throw new RuntimeException(e);
}
@@ -42,7 +45,7 @@ public static JsonNode toJson(final Object data) {
* Creates a new empty ObjectNode.
*/
public static ObjectNode newObject() {
- return new ObjectMapper().createObjectNode();
+ return mapper().createObjectNode();
}
/**
@@ -57,10 +60,79 @@ public static String stringify(JsonNode json) {
*/
public static JsonNode parse(String src) {
try {
- return new ObjectMapper().readValue(src, JsonNode.class);
+ return mapper().readValue(src, JsonNode.class);
} catch(Throwable t) {
throw new RuntimeException(t);
}
}
-
-}
+
+ /**
+ * Get the ObjectMapper that Play uses to parse/generate JSON. Note that although ObjectMapper instances are
+ * mutable and can be reconfigured, it is strongly advised that you don't change the configuration of the returned
+ * ObjectMapper. If you need to change the configuration, write a {@link ObjectMapperProvider}, and define it in
+ * your configuration using the <code>json.objectMapperProvider</code> property.
+ *
+ * @return Plays ObjectMapper
+ */
+ public static ObjectMapper mapper() {
+ if (play.api.Play.maybeApplication().isDefined()) {
+ return Play.application().plugin(JsonPlugin.class).objectMapper;
+ } else {
+ return defaultObjectMapper;
+ }
+ }
+
+ /**
+ * The default object mapper. Used if no current running application can be found, so that Json can be used, for
+ * example, in unit tests without having to start a fake application.
+ */
+ private static final ObjectMapper defaultObjectMapper = new ObjectMapper();
+
+ /**
+ * Provider for providing a custom ObjectMapper to be used by {@link play.libs.Json}.
+ */
+ public interface ObjectMapperProvider {
+ /**
+ * Provide an object mapper. This will be called when the application starts up.
+ *
+ * @return The object mapper.
+ */
+ ObjectMapper provide();
+ }
+
+ public static class JsonPlugin extends Plugin {
+ private final Application app;
+
+ private volatile ObjectMapper objectMapper;
+
+ public JsonPlugin(Application app) {
+ this.app = app;
+ }
+
+ public void onStart() {
+ String objectMapperProvider = app.configuration().getString("json.objectMapperProvider");
+ if (objectMapperProvider == null) {
+ objectMapper = defaultObjectMapper;
+ } else {
+ try {
+ objectMapper = app.classloader().loadClass(objectMapperProvider).asSubclass(ObjectMapperProvider.class)
+ .newInstance().provide();
+ } catch (InstantiationException e) {
+ throw new IllegalArgumentException("Unable to instantiate ObjectMapperProvider", e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException("Unable to instantiate ObjectMapperProvider", e);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Configured ObjectMapperProvider not found: " + objectMapperProvider, e);
+ } catch (ClassCastException e) {
+ throw new IllegalArgumentException(
+ "Configured ObjectMapperProvider is not of type ObjectMapperProvider: " + objectMapperProvider, e);
+ }
+ }
+ }
+
+ @Override
+ public void onStop() {
+ objectMapper = null;
+ }
+ }
+}
View
3 framework/src/play/src/main/java/play/libs/WS.java
@@ -394,9 +394,8 @@ public Document asXml() {
*/
public JsonNode asJson() {
String json = getBody();
- ObjectMapper mapper = new ObjectMapper();
try {
- return mapper.readValue(json, JsonNode.class);
+ return Json.parse(json);
} catch (Exception e) {
throw new RuntimeException(e);
}
View
1 framework/src/play/src/main/resources/play.plugins
@@ -5,4 +5,5 @@
500:play.api.db.evolutions.EvolutionsPlugin
600:play.api.cache.EhCachePlugin
1000:play.api.libs.concurrent.AkkaPlugin
+1100:play.libs.Json$JsonPlugin
10000:play.api.GlobalPlugin
View
90 framework/test/integrationtest-java/test/JsonTest.java
@@ -0,0 +1,90 @@
+package test;
+
+import org.codehaus.jackson.JsonGenerator;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.Version;
+import org.codehaus.jackson.map.JsonSerializer;
+import org.codehaus.jackson.map.Module;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.SerializerProvider;
+import org.codehaus.jackson.map.module.SimpleModule;
+import org.codehaus.jackson.map.module.SimpleSerializers;
+import org.junit.Test;
+import play.libs.Json;
+import play.test.*;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+import static play.test.Helpers.*;
+
+public class JsonTest {
+ @Test
+ public void noApp() {
+ assertThat(Json.parse("{\"foo\":\"bar\"}").get("foo").getTextValue(), equalTo("bar"));
+ }
+
+ @Test
+ public void inApp() {
+ running(fakeApplication(), new Runnable() {
+ public void run() {
+ assertThat(Json.parse("{\"foo\":\"bar\"}").get("foo").getTextValue(), equalTo("bar"));
+ }
+ });
+ }
+
+ @Test
+ public void inAppWithCustomObjectMapper() {
+ Map<String, String> config = new HashMap<String, String>();
+ config.put("json.objectMapperProvider", MockObjectMapperProvider.class.getName());
+ running(fakeApplication(config), new Runnable() {
+ public void run() {
+ assertThat(Json.toJson(new MockObject()).getTextValue(), equalTo("mockobject"));
+ }
+ });
+ }
+
+ public static class MockObjectMapperProvider implements Json.ObjectMapperProvider {
+ @Override
+ public ObjectMapper provide() {
+ Module module = new Module() {
+ @Override
+ public String getModuleName() {
+ return "mock";
+ }
+
+ @Override
+ public Version version() {
+ return new Version(1, 0, 0, "");
+ }
+
+ @Override
+ public void setupModule(SetupContext context) {
+ SimpleSerializers serializers = new SimpleSerializers();
+ serializers.addSerializer(MockObject.class, new MockObjectSerializer());
+ context.addSerializers(serializers);
+ }
+ };
+ return new ObjectMapper().withModule(module);
+ }
+ }
+
+ public static class MockObjectSerializer extends JsonSerializer<MockObject> {
+ @Override
+ public void serialize(test.JsonTest.MockObject value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
+ jgen.writeString("mockobject");
+ }
+
+ @Override
+ public Class<test.JsonTest.MockObject> handledType() {
+ return MockObject.class;
+ }
+ }
+
+ public static class MockObject {
+ public String foo = "bar";
+ }
+}
Something went wrong with that request. Please try again.