Spring #ConfigurationProperties instance to JSON with jackson: No serializer found - json

I'm having the following code:
#Data
#Validated
#ConfigurationProperties
public class Keys {
private final Key key = new Key();
#Data
#Validated
#ConfigurationProperties(prefix = "key")
public class Key {
private final Client client = new Client();
private final IntentToken intentToken = new IntentToken();
private final Intent intent = new Intent();
private final OAuth oauth = new OAuth();
private final ResourceToken resourceToken = new ResourceToken();
#Valid #NotNull private String authorization;
#Valid #NotNull private String bearer;
...
}
}
That is an instance representing a properties file such as:
key.authorization=Authorization
key.bearer=Bearer
..
As I can have different sources for the properties (properties file, MongoDB, etc), I have a client that inherit from Keys as follow:
Properties files source
#Component
#Configuration
#Primary
#PropertySource("classpath:${product}-keys.${env}.properties")
//#JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class CustomerKeysProperties extends Keys {
}
Mongo source
#Data
#EqualsAndHashCode(callSuper=true)
#Component
//#Primary
#Document(collection = "customerKeys")
public class CustomerKeysMongo extends Keys {
#Id
private String id;
}
I just select the source I want to use annotating the class with #Primary. In the example above, CustomerKeysProperties is the active source.
All this work fine.
The issue I have is when I try to convert an instance of CustomerKeysProperties into JSON, as in the code below:
#SpringBootApplication
public class ConverterUtil {
public static void main(String[] args) throws Exception {
SpringApplication.run(ConverterUtil.class, args);
}
#Component
class CustomerInitializer implements CommandLineRunner {
#Autowired
private Keys k;
private final ObjectMapper mapper = new ObjectMapper();
#Override
public void run(String... args) throws Exception {
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
//mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
String jsonInString = mapper.writeValueAsString(k);
System.out.println(jsonInString);
}
}
}
While k contains all the properties set, the conversion fails:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: x.client.customer.properties.CustomerKeysProperties$$EnhancerBySpringCGLIB$$eda308bd["CGLIB$CALLBACK_0"]->org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor["advised"]->org.springframework.aop.framework.ProxyFactory["targetSource"]->org.springframework.aop.target.SingletonTargetSource["target"]->x.client.customer.properties.CustomerKeysProperties$$EnhancerBySpringCGLIB$$4fd6c568["CGLIB$CALLBACK_0"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)
And if I uncomment
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
as suggested in the logs, I have an infinite loop happening in Jackson causing a stackoverflow:
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
..
Questions
At the end, I just want to provide an Util class than can convert a properties file in a JSON format that will be stored in MongoDB.
How can I solve this problem ?
Without passing through the object above, how can I transform a properties file into JSON ?
Can I save an arbitrary Java bean in MongoDB, with the conversion to JSON automagically done ?
The answer to any of the 3 questions above would be helpful.
Notes
To be noted that I use lombok. Not sure if this is the problem.
Another guess is that I'm trying to serialize a Spring managed bean and the proxy it involve cause jackson to not be able to do the serialization ? If so, what can be the turn-around ?
Thanks!

So found the problem:
jackson can't process managed bean.
The turn around was
try (InputStream input = getClass().getClassLoader().getResourceAsStream("foo.properties")) {
JavaPropsMapper mapper = new JavaPropsMapper();
Keys keys = mapper.readValue(input, Keys.class);
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String res = ow.writeValueAsString(keys);
System.out.println(res);
} catch (IOException e) {
e.printStackTrace();
}
where Keys was the Spring managed bean I was injecting.
And:
JavaPropsMapper come from:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-properties</artifactId>
</dependency>

Related

Direct self-reference leading to cycle (through reference chain: Issue with Jackson-databind)

I am working on serializing a Java object using Jackson-databind objectMapper.
My class structure is:
public class Car
{
private String color;
private String type;
private String model;
private Car thisCar;
}
Serialization method:
private static void serialize () throws IOException {
Car car = getCarData();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValue(new File("car.json"), car);
System.out.println("Object got serialized");
}
private static Car getCarData () {
Car car = new Car("Red", "Sedan");
Wheels wheels = new Wheels("Front", "Rear", null);
car.setThisCar(car);
return car;
}
While executing the serialize(), getting:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Direct self-reference leading to cycle (through reference chain: <package>.Car["thisCar"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1300)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter._handleSelfReference(BeanPropertyWriter.java:944)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:722)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectMapper._writeValueAndClose(ObjectMapper.java:4568)
at com.fasterxml.jackson.databind.ObjectMapper.writeValue(ObjectMapper.java:3763)
at com.informatica.service.p2i.Serializer.serialize(Serializer.java:46)
at com.informatica.service.p2i.Serializer.main(Serializer.java:29)
I know in the Car class, there is a reference to self. We have a similar class structure in one of the projects which needs to be addressed with Jackson-databind serializer.
Can anyone help on resolving this self-reference issue with jackson.
I tried #JsonManagedReference and #JsonBackReference. But in an object like this Car, how can I use #JsonBackReference as the field object is referencing to its own class.

#NestedConfigurationProperty and Converter Doesn't Work

I guess I have a rather complex configuration structure that I can't get to work. Here are the important pieces of the configuration classes:
#ConfigurationProperties
public abstract class AbstractConfigHolder<T extends AbstractComponentConfig> {
}
#Component
public class ExportConfigHolder extends AbstractConfigHolder<GenericExportConfig> {
#NestedConfigurationProperty
private Map<String, GenericExportConfig> exports;
// getters and setters for all fields
}
public class GenericExportConfig extends AbstractComponentConfig {
#NestedConfigurationProperty
private AbstractLocatedConfig target;
// getters and setters for all fields
}
public abstract class AbstractLocatedConfig extends RemoteConfig {
#NestedConfigurationProperty
private ProxyConfig proxy;
// getters and setters for all fields
}
public class ProxyConfig extends RemoteConfig {
private Type type;
// getters and setters for all fields
}
public class RemoteConfig {
private String host;
private int port;
private String user;
private String password;
// getters and setters for all fields
}
Here's the properties file:
exports.mmkb.name=MMKB
exports.mmkb.target=ftp
exports.mmkb.target.path=${user.home}/path/blah
# throws an exception:
exports.mmkb.target.proxy.host=super-host
The conversion stuff is what IMHO should cover everything and provide the proper beans to Spring:
#Configuration
public class ConversionSupport {
#ConfigurationPropertiesBinding
#Bean
public Converter<String, AbstractLocatedConfig> locatedConfigConverter(ApplicationContext applicationContext) {
return new Converter<String, AbstractLocatedConfig>() {
private ProxyConfigs proxyConfigs;
private ConnectionConfigs connectionConfigs;
#Override
public AbstractLocatedConfig convert(String targetType) {
System.out.println("Converting " + targetType);
initFields(applicationContext);
switch (targetType.toLowerCase()) {
case "ftp":
return new FtpTargetConfig(proxyConfigs, connectionConfigs);
// others...
}
}
// This is necessary to avoid conflicts in bean dependencies
private void initFields(ApplicationContext applicationContext) {
if (proxyConfigs == null) {
AbstractConfigHolder<?> configHolder = applicationContext.getBean(AbstractConfigHolder.class);
proxyConfigs = configHolder.getProxy();
connectionConfigs = configHolder.getConnection();
}
}
};
}
}
However, I get this instead:
Converting ftp
2016-04-29 09:33:23,900 WARN [org.springframework.context.annotation.AnnotationConfigApplicationContext] [main] Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'exportConfigHolder': Could not bind properties to ExportConfigHolder (prefix=, ignoreInvalidFields=false, ignoreUnknownFields=true, ignoreNestedProperties=false); nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'exports[mmkb].target.proxy[host]' of bean class [at.a1.iap.epggw.exporter.config.GenericExportConfig]: Property referenced in indexed property path 'proxy[host]' is neither an array nor a List nor a Map; returned value was [at.a1.iap.epggw.commons.config.properties.ProxyConfig#52066604]
2016-04-29 09:33:23,902 ERROR [org.springframework.boot.SpringApplication] [main] Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'exportConfigHolder': Could not bind properties to ExportConfigHolder (prefix=, ignoreInvalidFields=false, ignoreUnknownFields=true, ignoreNestedProperties=false); nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'exports[mmkb].target.proxy[host]' of bean class [at.a1.iap.epggw.exporter.config.GenericExportConfig]: Property referenced in indexed property path 'proxy[host]' is neither an array nor a List nor a Map; returned value was [at.a1.iap.epggw.commons.config.properties.ProxyConfig#52066604]
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:339)
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:289)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:408)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766)
at org.springframework.boot.SpringApplication.createAndRefreshContext(SpringApplication.java:361)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191)
at at.a1.iap.epggw.exporter.Application.main(Application.java:23)
Caused by: org.springframework.beans.InvalidPropertyException: Invalid property 'exports[mmkb].target.proxy[host]' of bean class [at.a1.iap.epggw.exporter.config.GenericExportConfig]: Property referenced in indexed property path 'proxy[host]' is neither an array nor a List nor a Map; returned value was [at.a1.iap.epggw.commons.config.properties.ProxyConfig#52066604]
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:406)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:280)
at org.springframework.boot.bind.RelaxedDataBinder$RelaxedBeanWrapper.setPropertyValue(RelaxedDataBinder.java:700)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:95)
at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:834)
at org.springframework.validation.DataBinder.doBind(DataBinder.java:730)
at org.springframework.boot.bind.RelaxedDataBinder.doBind(RelaxedDataBinder.java:128)
at org.springframework.validation.DataBinder.bind(DataBinder.java:715)
at org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget(PropertiesConfigurationFactory.java:269)
at org.springframework.boot.bind.PropertiesConfigurationFactory.bindPropertiesToTarget(PropertiesConfigurationFactory.java:241)
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:334)
... 17 common frames omitted
I mean the error clearly expresses that so far it all worked, there is a proper object in place, but somehow it fails to further apply the properties. I know that it's neither an array nor a List nor a Map, because I want it to be POJO.
What can I do here to make this work?
This is Spring-boot 1.3.3 BTW.
Well, it seems as if I somehow hit a corner-case where Spring doesn't do much about it. The main problem is that Spring seems to collect the available bean structure including their nested field structure before it knows of (or at least makes use of) the Converters lying around in the system.
I let the class with #ConfigurationProperties implement ApplicationContextAware and the new method
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
AnnotationConfigApplicationContext context = (AnnotationConfigApplicationContext) applicationContext;
#SuppressWarnings("unchecked")
Converter<String, AbstractLocatedConfig> locatedConfigSupport = context.getBean("locatedConfigConverter", Converter.class);
:
}
then also looked for all properties in the context's environment that would trigger the conversion process, manually called the conversion and created the bean structure that way.
For some reason the following lifecycle-stuff of Spring caused not all properties to end up in the bean, which made me do this:
#Configuration
public class SampleConfiguration {
#Autowired
private Environment environment;
#Autowired
private ClassWithTheConfigurationPropertiesAbove theBeanWithTheConfigurationPropertiesAbove;
#PostConstruct
void postConstruct() throws Exception {
if (environment instanceof AbstractEnvironment) {
MutablePropertySources sources = ((AbstractEnvironment) environment).getPropertySources();
// This is a MUST since Spring calls the nested properties handler BEFORE
// calling the conversion service on that field. Therefore, our converter
// for AbstractLocatedConfigs is called too late the first time. A second
// call will fill in the fields in the new objects and set the other ones
// again, too.
// See org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(String, Class<T>, boolean)
// Note: in case Spring reorders this, the logic here won't be needed.
setProperties(theBeanWithTheConfigurationPropertiesAbove, sources);
} else {
throw new IllegalArgumentException("The environment must be an " + AbstractEnvironment.class.getSimpleName());
}
}
void setProperties(Object target, MutablePropertySources propertySources) {
// org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget()
// was the base for this. Go there for further logic if needed.
RelaxedDataBinder dataBinder = new RelaxedDataBinder(target);
dataBinder.bind(new MutablePropertyValues(getProperties(propertySources)));
}
public String getProperty(String propertyName) {
return environment.getProperty(propertyName);
}
private Map<String, String> getProperties(MutablePropertySources propertySources) {
Iterable<PropertySource<?>> iterable = () -> propertySources.iterator();
return StreamSupport.stream(iterable.spliterator(), false)
.map(propertySource -> {
Object source = propertySource.getSource();
if (source instanceof Map) {
#SuppressWarnings("unchecked")
Map<String, String> sourceMap = (Map<String, String>) source;
return sourceMap.keySet();
} else if (propertySource instanceof SimpleCommandLinePropertySource) {
return Arrays.asList(((SimpleCommandLinePropertySource) propertySource).getPropertyNames());
} else if (propertySource instanceof RandomValuePropertySource) {
return null;
} else {
throw new NotImplementedException("unknown property source " + propertySource.getClass().getName() + " or its source " + source.getClass().getName());
}
})
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toMap(Function.identity(), this::getProperty));
}
}
It would be nice if Spring could do something about this to make it easier...

Trouble getting desired json output with JacksonJaxbJsonProvider

I'm using the latest Jackson (2.2.3) with a CXF application.
Here is my Jackson provider:
public class CustomJacksonJsonProvider extends JacksonJaxbJsonProvider {
public CustomJacksonJsonProvider() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
JaxbAnnotationModule jaxbModule = new JaxbAnnotationModule();
mapper.registerModule(jaxbModule);
this._mapperConfig.setMapper(mapper);
}
}
I have the following annotated class.
#XmlType(name = "configInfo")
#XmlRootElement(name = "configInfo")
public class ConfigInfo {
#XmlElement(name = "foo")
private String foo;
#XmlElementWrapper(name = "devices")
#XmlElement(name = "device")
private List<Device> devices;
public final List<Device> getDevices() {
if (devices == null)
devices = new ArrayList<Device>();
return devices;
}
}
I created an instance with no "foo" value, and one device in the devices list. When I render this, I get the following:
{"device":[{"name":"abc","type":"def"}]}
How can I make "device" render as "devices"?
I've managed to figure this out. The key realization is that if the JAXB annotations are confusing Jackson, then perhaps I should just have Jackson ignore them. I simply removed the registration of the "JaxbAnnotationModule" and now both my JSON and XML output are sane. I now need to consider whether it makes any sense to use "JacksonJaxbJsonProvider" as opposed to a simpler provider.

Jax-rs unmarshal json - custom type

Using jax-rs, I'm not sure how to manually unmarshal JSON into my custom Java objects.
From my browser I'm sending a simple put request with the following JSON:
{"myDate":{"dayOfMonth":23, "monthOfYear":7, "year":2011}}
On the server I have a BlahResource which consumes this JSON and prints out the Java object properties:
#Component
#Scope("request")
#Path("/blah")
#Consumes("application/json")
#Produces("application/json")
public class BlahResource {
#PUT
public String putBlah(Blah blah) {
System.out.println("Value: " + blah.getMyDate().getMonthOfYear() + "/" + blah.getMyDate().getDayOfMonth() + "/" + blah.getMyDate().getYear());
return "{}";
}
}
Here's the source code for Blah:
public class Blah {
private LocalDate myDate;
public Blah()
{
}
public void setMyDate(LocalDate myDate)
{
this.myDate = myDate;
}
public LocalDate getMyDate()
{
return myDate;
}
}
The problem is Blah.myDate is a Joda-time LocalDate class which does not have setters for dayOfMonth, monthOfYear, and year. So for instance, when I run this the following exception is thrown:
Jul 10, 2011 8:40:33 AM
com.sun.jersey.spi.container.ContainerResponse mapMappableContainerException
SEVERE: The exception contained within MappableContainerException could not
be mapped to a response, re-throwing to the HTTP container
org.codehaus.jackson.map.exc.UnrecognizedPropertyException:
Unrecognized field "dayOfMonth"
This makes perfect sense to me. The problem is I have no idea how to write some sort of adapter so that whenever the type LocalDate is encountered, my adapter class is used to convert the JSON into a LocalDate.
Ideally, I want to do something like this:
public class LocalDateAdapter {
public LocalDate convert(String json)
{
int dayOfMonth = (Integer)SomeJsonUtility.extract("dayOfMonth");
int year = (Integer)SomeJsonUtility.extract("year");
int monthOfYear = (Integer)SomeJsonUtility.extract("monthOfYear");
return new LocalDate(year, monthOfYear, dayOfMonth);
}
}
UPDATE
I've now tried two methods, neither seem to be working.
1) Using ObjectMapper
It seems all I need to do is get a handle on the ObjectMapper and add a deserializer. So I created this provider. To my surprise, I named my dserializer: LocalDateDeserializer and when I had eclipse auto-fix imports I was shocked to see that Jackson already provides an extension for Joda. When I start the server, it finds the provider, but otherwise it seems this code is never invoked.
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ext.JodaDeserializers.LocalDateDeserializer;
import org.codehaus.jackson.map.module.SimpleModule;
import org.joda.time.LocalDate;
import org.springframework.stereotype.Component;
#Component
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type) {
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null))
.addDeserializer(LocalDate.class, new LocalDateDeserializer());
mapper.registerModule(testModule);
return mapper;
}
}
2) The second method I tried is to specify a #JsonDeserialize annotation directly on the field.
#JsonDeserialize(using = CustomDateDeserializer.class)
private LocalDate myDate;
This also didn't seem to be invoked.
public class CustomDateDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException
{
return new LocalDate(2008, 2, 5);
}
}
I'm not sure what to do. This seems like a very basic problem.
UPDATE 2
I'm considering dropping Jackson for using deserialization (even though it works fairly well with Jersey).
I was already using flexjson for serialization, and it seems flexjson is just as simple for deserialization. All these other libraries have some much abstraction and unnecessary complexity.
In Flexjson, I just had to implement ObjectFactory:
class LocalDateTransformer implements ObjectFactory {
#Override
public Object instantiate(ObjectBinder context, Object value, Type targetType, Class targetClass)
{
HashMap map = (HashMap)value;
int year = (Integer)map.get("year");
int monthOfYear = (Integer)map.get("monthOfYear");
int dayOfMonth = (Integer)map.get("dayOfMonth");
return new LocalDate(year, monthOfYear, dayOfMonth);
}
}
It looks surprisingly like the "adapter" class I originally posted! And my resource method now becomes:
#PUT
public String putBlah(String blahStr) {
Blah blah = new JSONDeserializer<Blah>().use(LocalDate.class, new LocalDateTransformer()).deserialize(blahStr, Blah.class);
}

Jersey / JAXB: Unmarshaling of empty json array results in a list with one item where all fields are set to null

I have a really simple rest web service returning a list of questions. This code works as expected when the number of questions returned are greater than zero. But if the server returns an empty json array like [], JAXB creates a list with one question instance where all fields are set to null!
I'm new to both Jersey and JAXB so I don't know whether I haven't configured it correctly or whether this is a known problem. Any tips?
Client configuration:
DefaultApacheHttpClientConfig config = new DefaultApacheHttpClientConfig();
config.getProperties().put(DefaultApacheHttpClientConfig.PROPERTY_HANDLE_COOKIES, true);
config.getClasses().add(JAXBContextResolver.class);
//config.getClasses().add(JacksonJsonProvider.class); // <- Jackson causes other problems
client = ApacheHttpClient.create(config);
JAXBContextResolver:
#Provider
public final class JAXBContextResolver implements ContextResolver<JAXBContext> {
private final JAXBContext context;
private final Set<Class> types;
private final Class[] cTypes = { Question.class };
public JAXBContextResolver() throws Exception {
this.types = new HashSet(Arrays.asList(cTypes));
this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), cTypes);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return (types.contains(objectType)) ? context : null;
}
}
Client code:
public List<Question> getQuestionsByGroupId(int id) {
return digiRest.path("/questions/byGroupId/" + id).get(new GenericType<List<Question>>() {});
}
The Question class is just a simple pojo.
I know this is not exactly an answer to your question, but I choosed to use GSON on top of jersey, for my current projects. (and I try to avoid JAXB as much as possible), and I found it very easy and resilient.
You just have to declare
#Consumes(MediaType.TEXT_PLAIN)
or
#Produces(MediaType.TEXT_PLAIN)
or both, and use the GSON marshaller/unmarshaller, and work with plain Strings. Very easy to debug, unittest too...
Using Jackson may help.
See org.codehaus.jackson.map.ObjectMapper and org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_EMPTY
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
public class SampleContextResolver implements ContextResolver<ObjectMapper>
{
#Override
public ObjectMapper getContext(Class<?> type)
{
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationConfig(mapper.getSerializationConfig()
.withSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY)
}
}