I have a dependency to the third-party library Woorea Openstack-SDK (https://github.com/woorea/openstack-java-sdk) which uses Jackson 1.x annotations. Because of the Jackson update (Jackson 1.x -> Jackson 2.x) in Jersey 2.9, the Openstack-SDK becomes incompatible.
Is there a way to use Jersey 2.9 together with Jackson 1.x as JSON provider?
I used https://github.com/FasterXML/jackson-jaxrs-providers/ to provide Jackson 2.x to previous versions of Jersey (wihtout Jackson 2.x support). Should be an alternative to provide now Jackson 1.x to new versions of Jersey(with Jackson 2.0 support). Otherwise, check the implementation in the above link. You could do the same, since it's mostly about registering a new provider.
I found a solution... I removed the dependency to the artifact jersey-media-json-jackson and registered the following feature:
package test;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider;
import org.codehaus.jackson.jaxrs.JsonMappingExceptionMapper;
import org.codehaus.jackson.jaxrs.JsonParseExceptionMapper;
import org.glassfish.jersey.CommonProperties;
import org.glassfish.jersey.internal.InternalProperties;
import org.glassfish.jersey.internal.util.PropertiesHelper;
public class Jackson1xFeature implements Feature {
private final static String JSON_FEATURE = Jackson1xFeature.class.getSimpleName();
#Override
public boolean configure(final FeatureContext context) {
final Configuration config = context.getConfiguration();
final String jsonFeature = CommonProperties.getValue(config.getProperties(), config.getRuntimeType(), InternalProperties.JSON_FEATURE, JSON_FEATURE, String.class);
// Other JSON providers registered.
if (!JSON_FEATURE.equalsIgnoreCase(jsonFeature)) {
return false;
}
// Disable other JSON providers.
context.property(PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE, config.getRuntimeType()), JSON_FEATURE);
// Register Jackson.
if (!config.isRegistered(JacksonJaxbJsonProvider.class)) {
// add the default Jackson exception mappers
context.register(JsonParseExceptionMapper.class);
context.register(JsonMappingExceptionMapper.class);
context.register(JacksonJaxbJsonProvider.class, MessageBodyReader.class, MessageBodyWriter.class);
}
return true;
}
}
Related
I use Camel 2.16.0 for a Camel Rest project. I have introduced an abstract type that I need a custom deserializer to handle. This works as expected in my deserialization unit tests where I register my custom deserializer to the Objectmapper for the tests. To my understanding it is possible to register custom modules to the Jackson Objectmapper used by Camel as well (camel json).
My configuration:
...
<camelContext id="formsContext" xmlns="http://camel.apache.org/schema/spring">
...
<dataFormats>
<json id="json" library="Jackson" useList="true" unmarshalTypeName="myPackage.model.CustomDeserialized" moduleClassNames="myPackage.MyModule" />
</dataFormats>
</camelContext>
My module:
package myPackage;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class MyModule extends SimpleModule {
public MyModule() {
super();
addDeserializer(CustomDeserialized.class, new MyDeserializer());
}
}
The Camel rest configuration:
restConfiguration()
.component("servlet")
.bindingMode(RestBindingMode.json)
.dataFormatProperty("prettyPrint", "true")
.contextPath("/")
.port(8080)
.jsonDataFormat("json");
When running the service and invoking a function that utilize the objectmapper I get the exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of myPackage.model.CustomDeserialized, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
Any suggestions on what is wrong with my setup?
I found this solution to the problem and used this implementation for my custom jackson dataformat:
public class JacksonDataFormatExtension extends JacksonDataFormat {
public JacksonDataFormatExtension() {
super(CustomDeserialized.class);
}
protected void doStart() throws Exception {
addModule(new MyModule());
super.doStart();
}
}
I have a jersey webservice running 1.17 and supports returning responses via both XML and JSON via the #Produces annotation. I am assuming it uses JAXB by default when returning JSON responses but I have no way to confirm it. As of now, my existing clients also use the same JAXB serializer/deserializer. I want to create a new client that uses Jackson without impacting the existing clients.
The JAXB JSON response is incompatible for Jackson for Maps. the JSON for a map using JAXB is of the form
"mapName":{"entry":[{"key":"key1","value":"value1"},{"key":"key2","value":"value2"}]}
and Jackson fails to parse this. Is there any way to make jackson parse this JSON?
Another Attempt: Switching Jersey to use Jackson
This isn't the preferred option but I tried setting "com.sun.jersey.api.json.POJOMappingFeature" to true to allow it to use Jackson for JSON Serialization/Deserialization however the service ends up returning 500s on response without logging any exceptions. the log4j logger level is set to TRACE. I enabled the ContainerRepsonseFilter to confirm 500s in the response and to my surprise, it logs the successful 2xx response. My guess is the problem occurs somewhere further down the stack but I don't know where.
I ended up with using MOXy which is able to parse the above json format.
#Provider
public class JsonMoxyConfigurationContextResolver implements ContextResolver {
private final MoxyJsonConfig config;
public JsonMoxyConfigurationContextResolver() {
final Map<String, String> namespacePrefixMapper = new HashMap<String, String>();
namespacePrefixMapper.put("http://www.w3.org/2001/XMLSchema-instance", "xsi");
config = new MoxyJsonConfig()
.setNamespacePrefixMapper(namespacePrefixMapper)
.setNamespaceSeparator(':');
}
#Override
public MoxyJsonConfig getContext(Class<?> objectType) {
return config;
}
}
and enabled it Jersey 2.x client using
cc.register(JsonMoxyConfigurationContextResolver.class);
The following example works in a Java EE6 (Glassfish3) project of mine but failed after I switched to Java EE7 (Glassfish4). The HTTP request returns "500 Internal Error" without any message in the Glassfish server log. The project was setup using NetBeans8 as Maven Web Project and has no special dependencies, beans.xml or other configuration.
#RequestScoped
#Path("generic")
public class GenericResource {
#GET
#Path("ping")
#Produces(APPLICATION_JSON)
public List<String> debugPing() {
return Arrays.asList("pong");
}
And then:
$ curl -v http://localhost:8080/mavenproject2/webresources/generic/ping
> GET /mavenproject2/webresources/generic/ping HTTP/1.1
...
< HTTP/1.1 500 Internal Server Error
As as I understand, all REST handling is done by the Jackson reference implementation and that Jackson uses Jersey as underlaying JSON library. One of the two is supposed to have some kind of provider for all basic data types. Only custom made classes need a self written ObjectMapper. Are these concepts still correct?
It took me some hours but I finally solved this question myself.
First fact is that the Glassfish4 JAX-RS implementation "Jersey" as switched its underlying JSON library from Jackson 1.x to Eclipselink MOXy. The latter seems not be able to convert Lists, Arrays and arbitrary POJOs to JSON out of the box. Therefore I tried to force JAX-RS to use Jackson 2.x and disable MOXy.
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
// This is Jackson 2.x, Jackson 1.x used org.codehaus.jackson!
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#ApplicationPath("rest")
public class RestConfig extends Application {
private final static Logger log = LoggerFactory.getLogger(RestConfig.class);
#Override
public Set<Object> getSingletons() {
Set<Object> set = new HashSet<>();
log.info("Enabling custom Jackson JSON provider");
set.add(new JacksonJsonProvider() /* optionally add .configure(SerializationFeature.INDENT_OUTPUT, true) */);
return set;
}
#Override
public Map<String, Object> getProperties() {
Map<String, Object> map = new HashMap<>();
log.info("Disabling MOXy JSON provider");
map.put("jersey.config.disableMoxyJson.server", true);
return map;
}
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new java.util.HashSet<>();
// ... add your own REST enabled classes here ...
return resources;
}
}
My pom.xml contains:
<dependency>
<!-- REST (Jackson as JSON mapper) -->
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<!-- REST (Jackson LowerCaseWithUnderscoresStrategy etc.) -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.3</version>
</dependency>
Hope this helps someone!
I am using #Responsebody String to send JSON.
part of my code is below.(I am sorry for open it all)
#RequestMapping(value = "/getSomeList", method = RequestMethod.GET ,
headers="Accept=application/json", produces = "text/plain;charset=UTF-8")
public #ResponseBody String getThumbList(
#RequestParam("con_id") String con_id) throws Exception{
return json;
}
And actually it sends Json. But my client requests Bson type. How can I change Json to Bson without editing global format(my json is actually just string and I heard that spring can not response bson. Is that right?).
You need to register a HttpMessageConverter that can convert to BSON.
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="MyBsonHttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
Unfortunaly Spring has no Bson Http Message Converter, so you have to implement your own. (Or have more luck then me while google for one).
This is an old question, but it is nigh the only thing I can find on the web on this topic.
So here's how to do it. Assuming you're using Jackson as your JSON library in Spring.
Add a dependency to Michel Krämer's bson4jackson library
Create a class like so:
import com.fasterxml.jackson.databind.ObjectMapper;
import de.undercouch.bson4jackson.BsonFactory;
import de.undercouch.bson4jackson.BsonGenerator;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import javax.annotation.Nonnull;
public class MappingJackson2BsonHttpMessageConverter
extends AbstractJackson2HttpMessageConverter
{
public MappingJackson2BsonHttpMessageConverter(#Nonnull Jackson2ObjectMapperBuilder builder) {
super(bsonObjectMapper(builder), MediaType.parseMediaType("application/bson"));
}
#Nonnull
private static ObjectMapper bsonObjectMapper(#Nonnull Jackson2ObjectMapperBuilder om){
BsonFactory f = new BsonFactory();
f.configure(BsonGenerator.Feature.ENABLE_STREAMING, true);
return om.factory(f).build();
}
}
Add it as a #Bean to your configuration, or annotate it with #Component and make sure it's in your #ComponentScan's path.
That's it. Now, if you declare your MVC endpoint with #RequestMapping(produces = "application/bson") (in some form or another), the output will be the BSON encoding of your ResponseEntity's body.
This works with Jackson-annotated Objects which you'd normally serialize to JSON; and all the configuration you did to the Jackson ObjectMapper through Spring, modules or annotations will apply.
It also works for input as well as for output.
It also works with Feign clients.
I assume it also works with Spring's RestTemplate.
I am trying to serialize a JAXB generated class. With Jettison, I am able to create a hash-map mapping the XML namespaces to any JSON prefix. With Jettison, I also get the case sensitivity right in the serialization. With JACKSON, it is all lower case. So, it seems that Jettison is able to understand XMLRootElement(name=…) much better.
How do I make JACKSON better understand JAXB annotations like XMLRootElement?
How do I provide JACKSON with a XML→JSON namespace mapper?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
EclipseLink MOXy is a JAXB (JSR-222) compliant implementation. In EclipseLink 2.4.0 we introduced JSON-binding. Since MOXy is a JAXB implementation you will find the JSON output MOXy produces will be very consistent with XML output based on the same metadata. I will demonstrate below with an example.
DOMAIN MODEL
Below is the domain model I will use for this answer. For more information on specifying namespace information in a JAXB model see: http://blog.bdoughan.com/2010/08/jaxb-namespaces.html
package-info
#XmlSchema(
namespace="http://www.example.com/A",
elementFormDefault=XmlNsForm.QUALIFIED,
xmlns={
#XmlNs(prefix="a",namespaceURI = "http://www.example.com/A"),
#XmlNs(prefix="b",namespaceURI = "http://www.example.com/B")
}
)
package forum13214306;
import javax.xml.bind.annotation.*;
Customer
package forum13214306;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
String firstName;
#XmlElement(namespace="http://www.example.com/B")
String lastName;
}
XML HANDLING
Below is an example of how the domain model corresponds to an XML representation.
Demo
package forum13214306;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum13214306/input.xml");
Customer customer = (Customer) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(customer, System.out);
}
}
input.xml/Output
<?xml version="1.0" encoding="UTF-8"?>
<a:customer xmlns:b="http://www.example.com/B" xmlns:a="http://www.example.com/A">
<a:firstName>Jane</a:firstName>
<b:lastName>Doe</b:lastName>
</a:customer>
JSON HANDLING - WITHOUT NAMESPACES
Namespaces aren't a JSON concept so I would recommend not simulating them if you can avoid this. Below I'll demonstrate that MOXy doesn't need them. Note the exact same domain model and JAXBContext is used here that was used for the XML document with namespaces.
jaxb.properties
To specify MOXy as your JSON provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/search/label/jaxb.properties).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
To enable JSON binding the MEDIA_TYPE property needs to be enable on the Marshaller and Unmarshaller.
package forum13214306;
import java.io.File;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
File json = new File("src/forum13214306/input.json");
Customer customer = (Customer) unmarshaller.unmarshal(json);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
marshaller.marshal(customer, System.out);
}
}
input.json/Output
Below is the input to and output from running the demo code. Note how there is no simulated namespace information in the JSON document.
{
"customer" : {
"firstName" : "Jane",
"lastName" : "Doe"
}
}
JSON HANDLING - WITH SIMULATED NAMESPACES
Demo
If you really want to simulate namespaces in your JSON document you can leverage the NAMESPACE_PREFIX_MAPPER property on the Marshaller and Unmarshaller to do so.
package forum13214306;
import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Map<String, String> namespaceToPrefixMap = new HashMap<String, String>(2);
namespaceToPrefixMap.put("http://www.example.com/A", "a");
namespaceToPrefixMap.put("http://www.example.com/B", "b");
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
unmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER, namespaceToPrefixMap);
File json = new File("src/forum13214306/input.json");
Customer customer = (Customer) unmarshaller.unmarshal(json);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespaceToPrefixMap);
marshaller.marshal(customer, System.out);
}
}
input.json/Output
{
"a.customer" : {
"a.firstName" : "Jane",
"b.lastName" : "Doe"
}
}
FOR MORE INFORMATION
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
JSON does not have namespaces, unlike XML. So why do you feel you need namespace mapping? Databinding means mapping between Java POJOs and data format, using features of the format. For XML this includes namespaces, choice of elements vs attributes and so on. With JSON much of this complexity is removed, meaning that property names are used as is.
As to JAXB annotations: Jackson has its own set of annotations that are better match, but if you do want to use JAXB annotations as additional or alternative configuration source, you will need to use JAXB Annotation module.
But as to using XMLRootElement, it is unnecessary for JSON: JSON Objects do not have name.
I do not know what you mean by "get the case sensitivity right in the serialization" -- right in what sense? What is the problem? You will need to give an example of POJO definition as well as expected JSON.
Finally, keep in mind that there is no need for JSON and XML notations to look like each other. These are different data formats with different logical data models, and naturally differing mappings: JSON has native distinction between Arrays and Objects, for example; whereas XML has to use Elements for both. And XML has namespaces, attributes, that JSON lacks. These lead to different natural mappings, and attempts to "unify" the two lead to somewhat unnatural results for one or the other, or both. In case of Jettison (and frameworks that use it), it's ugly JSON ("franken-JSON").