I'm extending following third-party class which I can't change:
public class Page {
#JsonProperty("content")
private String content;
public String getContent() {};
}
My implementation of Page looks like this:
public class MyPage extends Page {
#JsonProperty("my-content")
public String getContent() {return super.getContent()};
}
When I'm trying to serialize instance of MyPage class I get the following exception:
java.lang.IllegalStateException: Conflicting property name definitions:
'content' (for [field com.test.Page#content])
vs
'my-content' (for [method com.test.MyPage#getContent(0 params)])
Is there an easy way to force serializer to produce 'my-content' property?
I guess the issue was solved in Jackson 2.4.0. Please check https://github.com/FasterXML/jackson-databind/issues/193.
Try to update your Jackson library to 2.4.0 or above.
Related
I'm having an issue with building REST architecture for some legacy code.
Jackson ObjectMapper is unable to map my custom object to legacy object, because of 'enums' that really are classes with static final fields.
I tried implementing custom converters/deserializers with no success
In the legacy system there are enums that look like this:
public final class LegacyEnum extends LegacyEnumSuperclass {
public static final LegacyEnum VALUE = new LegacyEnum("1");
I'm receiving values of these 'enums' as Strings, that I convert to legacy enum values (custom deserializer) and set them in my custom class (I need it because I'm using jackson annotations, and I have no access or permission to modify legacy code) and this part works nicely. When I try to map my custom object to legacy object with
objectMapper.convertValue(myCustomObject, LegacyObjectContainingEnums.class);
I get an exception:
Can not construct instance of LegacyEnum: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
The LegacyEnum class has a private constructor, and LegacyEnumSuperclass has a similar protected constructor so I cannot access them (neither can ObjectMapper). I have tried implementing a custom converter, that would skip the 'create new object' part of ObjectMapper mapping, and I also tried to reuse my custom deserializer. I ran into multiple issues and achieved no success.
The most annoying part is, that when I use ModelMapper library it works like a charm (it probably just sets a value in the legacy object, no need to create new LegacyEnum instance like ObjectMapper!) but I'm trying to resolve that issue without adding new dependencies.
Any ideas?
I resolved the issue by using MixIn and custom deserializer, like this:
public abstract class LegacyClassMixIn
#JsonDeserialize(using = LegacyEnumDeserializer.class)
abstract LegacyEnum getLegacyEnum();
Deserializer:
public class LegacyEnumDeserializer extends JsonDeserializer<LegacyEnumSuperclass> implements ContextualDeserializer {
private JavaType valueType;
#Override
public JsonDeserializer createContextual(DeserializationContext context, BeanProperty property) {
JavaType wrapperType = property.getType();
LegacyEnumDeserializer deserializer = new LegacyEnumDeserializer();
deserializer.valueType = wrapperType;
return deserializer;
}
#Override
public LegacyEnumSuperclass deserialize(JsonParser parser, DeserializationContext context) throws IOException {
return LegacyEnumSuperclass.getEnum(valueType.getRawClass(), parser.readValueAs(String.class));
}
valueType.getRawClass() returns LegacyEnum.class, that way I can use one deserializer for all of the 'enums' that inherit LegacyEnumSuperclass class. getEnum is a custom method, from the legacy code.
Registering MixIn in ObjectMapper Spring configuration:
#Configuration
public class ObjectMapperConfig {
public ObjectMapperConfig(ObjectMapper objectMapper) {
objectMapper.addMixIn(LegacyClass.class, LegacyClassMixIn.class);
}
}
That way I can use LegacyClass as a parameter in a Controller method.
Thanks for clues.
Is there a simple way to use spring data couchbase with documents that do not have _class attribute?
In the couchbase I have something like this in my sampledata bucket:
{
"username" : "alice",
"created" : 1473292800000,
"data" : { "a": 1, "b" : "2"},
"type" : "mydata"
}
Now, is there any way to define mapping from this structure of document to Java object (note that _class attribute is missing and cannot be added) and vice versa so that I get all (or most) automagical features from spring couchbase data?
Something like:
If type field has value "mydata" use class MyData.java.
So when find is performed instead of automatically adding AND _class = "mydata" to generated query add AND type = "mydata".
Spring Data in general needs the _class field to know what to instantiate back when deserializing.
It's fairly easy in Spring Data Couchbase to use a different field name than _class, by overriding the typeKey() method in the AbsctractCouchbaseDataConfiguration.
But it'll still expect a fully qualified classname in there by default
Getting around that will require quite a bit more work:
You'll need to implement your own CouchbaseTypeMapper, following the model of DefaultCouchbaseTypeMapper. In the super(...) constructor, you'll need to provide an additional argument: a list of TypeInformationMapper. The default implementation doesn't explicitly provide one, so a SimpleTypeInformationMapper is used, which is the one that puts FQNs.
There's an alternative implementation that is configurable so you can alias specific classes to a shorter name via a Map: ConfigurableTypeInformationMapper...
So by putting a ConfigurableTypeInformationMapper with the alias you want for specific classes + a SimpleTypeInformationMapper after it in the list (for the case were you serialize a class that you didn't provide an alias for), you can achieve your goal.
The typeMapper is used within the MappingCouchbaseConverter, which you'll also need to extend unfortunately (just to instantiate your typeMapper instead of the default.
Once you have that, again override the configuration to return an instance of your custom MappingCouchbaseConverter that uses your custom CouchbaseTypeMapper (the mappingCouchbaseConverter() method).
You can achive this e.g. by creating custom annotation #DocumentType
#DocumentType("billing")
#Document
public class BillingRecordDocument {
String name;
// ...
}
Document will look like:
{
"type" : "billing"
"name" : "..."
}
Just create following classes:
Create custom AbstractReactiveCouchbaseConfiguration or AbstractCouchbaseConfiguration (depends which varian you use)
#Configuration
#EnableReactiveCouchbaseRepositories
public class CustomReactiveCouchbaseConfiguration extends AbstractReactiveCouchbaseConfiguration {
// implement abstract methods
// and configure custom mapping convereter
#Bean(name = BeanNames.COUCHBASE_MAPPING_CONVERTER)
public MappingCouchbaseConverter mappingCouchbaseConverter() throws Exception {
MappingCouchbaseConverter converter = new CustomMappingCouchbaseConverter(couchbaseMappingContext(), typeKey());
converter.setCustomConversions(customConversions());
return converter;
}
#Override
public String typeKey() {
return "type"; // this will owerride '_class'
}
}
Create custom MappingCouchbaseConverter
public class CustomMappingCouchbaseConverter extends MappingCouchbaseConverter {
public CustomMappingCouchbaseConverter(final MappingContext<? extends CouchbasePersistentEntity<?>,
CouchbasePersistentProperty> mappingContext, final String typeKey) {
super(mappingContext, typeKey);
this.typeMapper = new TypeBasedCouchbaseTypeMapper(typeKey);
}
}
and custom annotation #DocumentType
#Persistent
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
public #interface DocumentType {
String value();
}
Then create TypeAwareTypeInformationMapper which will just check if an entity is annoatated by #DocumentType if so, use value from that annotation, do the default if not (fully qualified class name)
public class TypeAwareTypeInformationMapper extends SimpleTypeInformationMapper {
#Override
public Alias createAliasFor(TypeInformation<?> type) {
DocumentType[] documentType = type.getType().getAnnotationsByType(DocumentType.class);
if (documentType.length == 1) {
return Alias.of(documentType[0].value());
}
return super.createAliasFor(type);
}
}
Then register it as following
public class TypeBasedCouchbaseTypeMapper extends DefaultTypeMapper<CouchbaseDocument> implements CouchbaseTypeMapper {
private final String typeKey;
public TypeBasedCouchbaseTypeMapper(final String typeKey) {
super(new DefaultCouchbaseTypeMapper.CouchbaseDocumentTypeAliasAccessor(typeKey),
Collections.singletonList(new TypeAwareTypeInformationMapper()));
this.typeKey = typeKey;
}
#Override
public String getTypeKey() {
return typeKey;
}
}
In your couchbase configuration class you just need to have :
#Override
public String typeKey() {
return "type";
}
Unfortunately for query derivation (n1ql) the _class or type are still using the class name.Tried spring couch 2.2.6 and it's minus point here.
#Simon, are you aware that something has changed and the support to have the possibility to have custom _class/type value in next release(s)?
#SimonBasle
Inside of class N1qlUtils and method createWhereFilterForEntity we have access to the CouchbaseConverter. On line:
String typeValue = entityInformation.getJavaType().getName();
Why not use the typeMapper from the converter to get the name of the entity when we want to avoid using the class name? Otherwise you have to annotate each method in your repository as follows:
#Query("#{#n1ql.selectEntity} WHERE `type`='airport' AND airportname = $1")
List<Airport> findAirportByAirportname(String airportName);
If createWhereFilterForEntity used the CouchbaseConverter we could avoid annotating with the #Query.
I'm trying to serialize an object in Java using Jackson, but when I'm trying to serialize it, it gives me this error:
No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer
I tried this post, but it didn't help.
Here is the class I'm trying to serialize:
public class Repository {
public String name;
#JsonIgnore // to avoid recursive calls
public ArrayList<UserRole> contributors = new ArrayList<UserRole>();
public User self;
public ArrayList<FileInfo> files;
public RepositoryType repositoryType;
public String path;
}
I also tried to create getters/setters for each field but still nothing.
Here is my serialization method:
public static String convertObjectToJson(Object object) throws IOException {
ObjectWriter objectWriter = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = objectWriter.writeValueAsString(object); //error on this line
return json;
}
Looks like your one of your classes has java.io.FileDescriptor reference.
By default, Jackson will only work with with fields that are either public, or have a public getter methods – serializing an entity that has all fields private or package private will fail
If you look at the source code of java.io.FileDescriptor you can see
there are private fields without public getters.
You should configure your objectMapper visibility to allow access to private fields also.
// For jackson 2.*
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
// For jackson lower than 2
objectMapper.setVisibility(JsonMethod.FIELD, Visibility.ANY);
I was facing problems to send objects to Thymeleaf template with ResponseEntity it was giving me exception "StackOverFlowError" while serializing and your note " #JsonIgnore // to avoid recursive calls" solved my problem. Thanks
i'm using Jersey to build a REST service and as Json Processor i set Jackson in my application.
#javax.ws.rs.ApplicationPath("/")
public class MyApplication extends ResourceConfig {
public MyApplication() {
packages("controller");
register(JacksonFeature.class);
}
I implement a ContextResolver for Jacksons ObjectMapper (as it's suggested in this post Configure Jersey/Jackson to NOT use #XmlElement field annotation for JSON field naming) which creates an ObjectMapper that doesn't fail on unknown properties during deserialization:
#Provider
public class MyJsonObjectMapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type)
{
System.out.println("mapper!!!");
ObjectMapper result = new ObjectMapper();
result.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return result;
}
}
and then i register this class in my application inserting register(MyJsonObjectMapperProvider.class) in the class MyApplication shown above. I obtain what i want, in sense that if there are unknown properties in the json the object mapper doesn't fail.
My problem is another; i have this class that i use to map a specified Json, in order to deserialize it and subsequently serialize it:
public class Version {
private String status;
private String updated;
private String id;
private List<Link> links;
#XmlElement(name = "media-types")
private List<MediaTypes> media_types;
//constructor + getter and setter
}
The problem is about the element media_types and the use of the annotation #XmlElement. Before i insert the ContextResolver to personalize ObjectMapper all works fine, in fact after serialization i obtain a json in which the element/attribute media_types has as name media-types; on the contrary with ContextResolver this element doesn't change it's name and has media_types. I think that, during serialization, the annotation XmlElement doesn't work, but i'm not sure that this is the correct reason.
Another attempt i try to do is to put #JsonProperty("media-types") annotation instead of #XmlElement annotation but with no result; in fact with this annotation i obtain also a Processing Exception.
The last attempt (in addition to what has been suggested by the previous post) was that of insert these lines of code in the ContextResolver:
AnnotationIntrospector intr = new AnnotationIntrospector.Pair(new JaxbAnnotationIntrospector(),new JacksonAnnotationIntrospector());
// usually we use same introspector(s) for both serialization and deserialization:
result.getDeserializationConfig().withAnnotationIntrospector(intr);
result.getSerializationConfig().withAnnotationIntrospector(intr);
in order to use both JaxbAnnotation and JacksonAnnotation but the name of the field in question remain media_types.
I hope i was clear in explain my problem and thanks you in advance for your help!
This is the same questions than :
Jackson JSON library: how to instantiate a class that contains abstract fields
Nevertheless its solution is not possible since my abstract class is in another project than the concrete one.
Is there a way then ?
EDIT
My architecture is as follows:
public class UserDTO {
...
private LanguageDTO lang;
}
I send that object user :
restTemplate.postForObject(this.getHttpCore().trim() + "admin/user/save/1/" + idUser, userEntity, UserDTO.class);
Then I am supposed to receive it in the function :
#RequestMapping(value = "/save/{admin}/{idUser}", method = RequestMethod.POST)
public String saveUserById(#RequestBody final UserEntity user, #PathVariable Integer idUser, #PathVariable boolean admin)
with UserEntity defined as :
public class UserEntity extends AbstractUserEntity {
...
}
public abstract class AbstractUserEntity {
...
private AbstractLanguageEntity lang;
}
I would like to know how I can specify that lang should be instantiate as LanguageEntity whereas abstract classes are in another project.
This could work assuming you can configure how the object get serialized. See the example here. Look under "1.1. Global default typing" to set the defaults to include extra information in your JSON string, basically the concrete Java type that must be used when deserializing.
Since it seems you need to do this for your Spring servlet, you would have to pass a Spring message converter as mentioned here
Then inside your custom objectMapper, you can do the necessary configuration:
public class JSONMapper extends ObjectMapper {
public JSONMapper() {
this.enableDefaultTyping();
}
}
You could probably also make it work with Mix-ins, which allow you to add annotations to classes already defined. You can see and example here. This will also need to be configured inside the objectMapper.
If you need the same functionality on your client side (REST template), you can pass the object mapper as shown here.
The easiest way to solve that issue is to add getters et setters in UserEntity but specifying a concrete class :
public LanguageEntity getLang() {
return (LanguageEntity) lang;
}
public void setLang(LanguageEntity language){
this.lang = language
}
If all that you want to achieve is to note that LanguageEntity is the implementation of AbstractLanguageEntity, you can register this mapping via module:
SimpleModule myModule = new SimpleModule())
.addAbstractTypeMapping(AbstractLanguageEntity.class,
LanguageEntity.class);
ObjectMapper mapper = new ObjectMapper()
.registerMdoule(myModule);