I am using the Jackson ObjectMapper to deserialize some JSON into a Java class, which we'll call PlayerData. I would like to add a bit of logic to the PlayerData class to fix up some data after the fields have been loaded in. For example, some early JSON files used to use a "sex" flag instead of a "gender" falg, so if the sex flag is set but the gender flag is not set, I'd like to set the value of the gender field to be the value of the sex field.
Is there some sort of #PostConstruct or #AfterLoad annotation that I could affix to a method? Or perhaps an interface that I could implement? I didn't notice one in the documentation, but it seemed like an obvious feature.
Found this thru a link in the comments (credit: fedor.belov). This appears to allow you to run code post construct.
Adding a comment for people who end up here via
http://jira.codehaus.org/browse/JACKSON-645 or
http://jira.codehaus.org/browse/JACKSON-538 and are looking for a
method which is called after a deserializer completes. I was able to
achieve the desired effect by including an annotation and writing a
converter which uses the same class as input and output.
#JsonDeserialize(converter=MyClassSanitizer.class) // invoked after class is fully deserialized
public class MyClass {
public String field1;
}
import com.fasterxml.jackson.databind.util.StdConverter;
public class MyClassSanitizer extends StdConverter<MyClass,MyClass> {
#Override
public MyClass convert(MyClass var1) {
var1.field1 = munge(var1.field1);
return var1;
}
}
If you're not using the #JsonCreator, then Jackson will use the setter and getter methods to set the fields.
So if you define the following methods assuming that you have Sex and Gender enums:
#JsonProperty("sex")
public void setSex(final Sex sex) {
this.sex = sex;
if (gender == null) {
gender = (sex == Sex.WOMAN) ? Gender.WOMAN : Gender.MAN;
}
}
#JsonProperty("gender")
public void setGender(final Gender gender) {
this.gender = gender;
if (sex == null) {
sex = (gender == Gender.WOMAN) ? Sex.WOMAN : Sex.MAN;
}
}
it would work.
Update: You can find all of the annotations of Jackson library here.
Update2: Other solution:
class Example {
private final Sex sex;
private final Gender gender;
#JsonCreator
public Example(#JsonProperty("sex") final Sex sex) {
super();
this.sex = sex;
this.gender = getGenderBySex(sex)
}
#JsonFactory
public static Example createExample(#JsonProperty("gender") final Gender gender) {
return new Example(getSexByGender(gender));
}
private static Sex getSexByGender(final Gender) {
return (gender == Gender.WOMAN) ? Sex.WOMAN : Sex.MAN;
}
private static Gender getGenderBySex(final Sex) {
return (sex == Sex.WOMAN) ? Gender.WOMAN : Gender.MAN;
}
}
This is not supported out of the box, but you can easily create your #JsonPostDeserialize annotation for methods to be called after deserialization.
First, define the annotation:
/**
* Annotation for methods to be called directly after deserialization of the object.
*/
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonPostDeserialize {
}
Then, add the following registration and implementation code to your project:
public static void addPostDeserializeSupport(ObjectMapper objectMapper) {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDescription,
JsonDeserializer<?> originalDeserializer) {
return new CustomAnnotationsDeserializer(originalDeserializer, beanDescription);
}
});
objectMapper.registerModule(module);
}
/**
* Class implementing the functionality of the {#link JsonPostDeserialize} annotation.
*/
public class CustomAnnotationsDeserializer extends DelegatingDeserializer {
private final BeanDescription beanDescription;
public CustomAnnotationsDeserializer(JsonDeserializer<?> delegate, BeanDescription beanDescription) {
super(delegate);
this.beanDescription = beanDescription;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new CustomAnnotationsDeserializer(newDelegatee, beanDescription);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Object deserializedObject = super.deserialize(p, ctxt);
callPostDeserializeMethods(deserializedObject);
return deserializedObject;
}
private void callPostDeserializeMethods(Object deserializedObject) {
for (AnnotatedMethod method : beanDescription.getClassInfo().memberMethods()) {
if (method.hasAnnotation(JsonPostDeserialize.class)) {
try {
method.callOn(deserializedObject);
} catch (Exception e) {
throw new RuntimeException("Failed to call #JsonPostDeserialize annotated method in class "
+ beanDescription.getClassInfo().getName(), e);
}
}
}
}
}
Finally, modify your ObjectMapper instance with addPostDeserializeSupport, it will invoke all #JsonPostDeserialize annotated method of deserialized objects.
This is something that has actually been suggested couple of times earlier. So maybe filing an RFE would make sense; there are multiple ways in which this could work: obvious ones being ability to annotate type (#JsonPostProcess(Processor.class)) and ability to register post-processor through Module API (so that there's basically a callback when Jackson constructs deserializer, to let module specify post-processor to use if any). But perhaps there are even better ways to do this.
Related
I need to edit the name of "existing field" in POJO instead of adding "extra_field". Is it possible with the approach referenced link below?
Please note I do not want to use #JsonProperty annotation.
Requirement is, I have a POJO and want to use different field name every time without change in POJO. For example I have a field c_id in POJO and some times it need to write as cust_id and another time it would be my_id.
Also note I cannot change implementation of POJO as it is already used in several modules and have generic implementation.
POJO Example:
class MyPojo {
String id;
// getter and setters
}
Expected output can be the following: (name of field can be changed)
{"cust_id": "123"}
{"my_id": "123"}
Mixins
The easiest way to modify the output of Jackson without adding annotations to the original POJO is using mixins.
Just define a mixin-class with the necessary annotations and indicate to Jackson that you want to use the mixin when serializing the original object.
private static class MyPojoMixin {
#JsonProperty("cust_id")
private String id;
}
public String serializeWithMixin(MyPojo p) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(MyPojo.class, MyPojoMixin.class);
return mapper.writeValueAsString(p);
}
Custom property naming strategy
If you need to programmatically change the field-name, you might not be able to use the mixin solution. You could then use a custom PropertyNamingStrategy:
public class IdRenamingStrategy extends PropertyNamingStrategy {
private final PropertyNamingStrategy inner;
private final String newIdPropertyName;
public IdRenamingStrategy(String newIdPropertyName) {
this(PropertyNamingStrategy.LOWER_CAMEL_CASE, newIdPropertyName);
}
public IdRenamingStrategy(PropertyNamingStrategy inner, String newIdPropertyName) {
this.inner = inner;
this.newIdPropertyName = newIdPropertyName;
}
private String translate(String propertyName) {
if ("id".equals(propertyName)) {
return newIdPropertyName;
} else {
return propertyName;
}
}
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
return inner.nameForField(config, field, translate(defaultName));
}
#Override
public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return inner.nameForGetterMethod(config, method, translate(defaultName));
}
#Override
public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
return inner.nameForSetterMethod(config, method, translate(defaultName));
}
#Override
public String nameForConstructorParameter(MapperConfig<?> config, AnnotatedParameter ctorParam, String defaultName) {
return inner.nameForConstructorParameter(config, ctorParam, translate(defaultName));
}
}
This can be used like this:
public String serializeWithPropertyNamingStrategy(MyPojo p) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new IdRenamingStrategy("cust_id"));
return mapper.writeValueAsString(p));
}
I have a java class like :
class TestJsonClass {
private String propertyA;
private String propertyB;
private String propertyC;
}
Now during runtime i want to give different property names for each of the property, and not a static one using #JsonProperty("sample")
How do I accomplish this? I am using Jackson library ad Spring MVC
Thanks in advance...
You can make use of Modules for this purpose. This is the easiest solutions to your problem. Here is an example:
A simple class that can carry your property-name-mappings for each request:
public class PropertyNameMapper {
// The class for which the mappings need to take place.
public Class<?> classToFilter;
// The mappings property names. Key would be the existing property name
// value would be name you want in the ouput.
public Map<String, String> nameMappings = Collections.emptyMap();
public PropertyNameMapper(Class<?> classToFilter, Map<String, String> nameMappings) {
this.classToFilter = classToFilter;
this.nameMappings = nameMappings;
}
}
A custom BeanPropertyWriter that will be used for specifying the output name for the properties.
public class MyBeanPropertyWriter extends BeanPropertyWriter {
// We would just use the copy-constructor rather than modifying the
// protected properties. This is more in line with the current design
// of the BeanSerializerModifier class (according to its documentation).
protected MyBeanPropertyWriter(BeanPropertyWriter base, String targetName) {
super(base, new SerializedString(targetName));
}
}
Now, a custom BeanSerializerModifier that is called each time to allow you to modify the serialized properties.
public class MySerializerModifier extends BeanSerializerModifier {
public List<BeanPropertyWriter> changeProperties(
SerializationConfig config, BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
List<PropertyNameMapper> propertyMappings = getNameMappingsFromRequest();
PropertyNameMapper mapping = mappingsForClass(propertyMappings,
beanDesc.getBeanClass());
if (mapping == null) {
return beanProperties;
}
List<BeanPropertyWriter> propsToWrite = new ArrayList<BeanPropertyWriter>();
for (BeanPropertyWriter propWriter : beanProperties) {
String propName = propWriter.getName();
String outputName = mapping.nameMappings.get(propName);
if (outputName != null) {
BeanPropertyWriter modifiedWriter = new MyBeanPropertyWriter(
propWriter, outputName);
propsToWrite.add(modifiedWriter);
} else {
propsToWrite.add(propWriter);
}
}
return propsToWrite;
}
private List<PropertyNameMapper> getNameMappingsFromRequest() {
RequestAttributes requestAttribs = RequestContextHolder
.getRequestAttributes();
List<PropertyNameMapper> nameMappings = (List<PropertyNameMapper>) requestAttribs
.getAttribute("nameMappings",
RequestAttributes.SCOPE_REQUEST);
return nameMappings;
}
private PropertyNameMapper mappingsForClass(
List<PropertyNameMapper> nameMappings, Class<?> beanClass) {
for (PropertyNameMapper mapping : nameMappings) {
if (mapping.classToFilter.equals(beanClass)) {
return mapping;
}
}
return null;
}
}
Now, you need a custom Module to be able to customize the output using the above created BeanSerializerModifier:
public class MyModule extends Module {
#Override
public String getModuleName() {
return "Test Module";
}
#Override
public void setupModule(SetupContext context) {
context.addBeanSerializerModifier(new MySerializerModifier());
}
#Override
public Version version() {
// Modify if you need to.
return Version.unknownVersion();
}
}
Now register this module with your ObjectMapper. You can get the Jackson HTTP message converter from your spring application context, and get its object mapper.
// Figure out a way to get the ObjectMapper.
MappingJackson2HttpMessageConverter converter = ... // get the jackson-mapper;
converter.getObjectMapper().registerModule(new MyModule())
And that's it. This is the easiest way to customize serialization of your properties dynamically.
To use this, create a List of PropertyNameMappers and add it as an attribute (named "nameMappings" in this example) in the current request.
This is an example, not production-ready code. You might probably need to add null-checks and things like that. Also, a few minor adjustments might be needed based on the version of the libraries you are using.
If the solution doesn't work for you, let me know the problems you are facing.
You could inject a custom PropertyNamingStrategy into the ObjectMapper that's used in deserialization.
Just set fields into the PropertyNamingStrategy at runtime, assuming you can map them to something like the default JsonPropertyName (e.g. propertyA, propertyB, propertyC).
public class MyNamingStrategy extends PropertyNamingStrategy {
String propertyAName, propertyBName, propertyCName;
public MyNamingStrategy(String propANm, String propBNm, String propCNm) {
this.propertyAName = propANm;
//finish
}
#Override
public String nameForField(MapperConfig<?> config, AnnotatedField field,
String defaultName) {
return convert(defaultName);
}
#Override
public String nameForGetterMethod(MapperConfig<?> config,
AnnotatedMethod method, String defaultName) {
return convert(defaultName);
}
#Override
public String nameForSetterMethod(MapperConfig<?> config,
AnnotatedMethod method, String defaultName) {
return convert(defaultName);
}
public String convert(String defaultName ){
return defaultName.replace("propertyA", propertyAName).replace( //finish
}
Finally you'd create an instance and inject it at runtime.
objectMapper.setNamingStrategy(myNamingStrategyInstance));
See this Cowtowncoder post for more on PropertyNamingStrategy:
Jackson 1.8: custom property naming strategies
Or this documentation:
github.com/FasterXML/jackson-docs/wiki/PropertyNamingStrategy
I know how to use a custom serializer in Jackson (by extending JsonSerializer), but I want the default serializer to work for all fields, except for just 1 field, which I want to override using the custom serializer.
Annotations are not an option, because I am serializing a generated class (from Thrift).
How do I specify only certain fields to be overridden when writing a custom jackson serializer?
Update:
Here's the class I want to serialize:
class Student {
int age;
String firstName;
String lastName;
double average;
int numSubjects
// .. more such properties ...
}
The above class has many properies, most of which use native types. I want to just override a few properties in the custom serializer and let Jackson deal with the rest as usual. For e.g. I just want to convert the "age" field to a custom output.
Assuming your Target class is
public class Student {
int age;
String firstName;
String lastName;
double average;
int numSubjects;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public double getAverage() {
return average;
}
public void setAverage(double average) {
this.average = average;
}
public int getNumSubjects() {
return numSubjects;
}
public void setNumSubjects(int numSubjects) {
this.numSubjects = numSubjects;
}
}
You need to write a custom serializer as given below
public class MyCustomSerializer extends JsonSerializer<Student> {
#Override
public void serialize(Student value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
if (value != null) {
jgen.writeStartObject();
jgen.writeStringField("age", "Age: " + value.getAge()); //Here a custom way to render age field is used
jgen.writeStringField("firstName", value.getFirstName());
jgen.writeStringField("lastName", value.getLastName());
jgen.writeNumberField("average", value.getAverage());
jgen.writeNumberField("numSubjects", value.getNumSubjects());
//Write other properties
jgen.writeEndObject();
}
}
}
then add it to the ObjectMapper
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("custom",
Version.unknownVersion());
module.addSerializer(Student.class, new MyCustomSerializer());
mapper.registerModule(module);
then use it like
Student s = new Student();
s.setAge(2);
s.setAverage(3.4);
s.setFirstName("first");
s.setLastName("last");
s.setNumSubjects(3);
StringWriter sw = new StringWriter();
mapper.writeValue(sw, s);
System.out.println(sw.toString());
It will produce a o/p like
{"age":"Age:
2","firstName":"first","lastName":"last","average":3.4,"numSubjects":3}
Just because you can not modify classes DOES NOT mean you could not use annotations: just use mix-in annotations. See this blog entry for example (or google for more with "jackson mixin annotations") for how to use this.
I have specifically used Jackson with protobuf- and thrift-generated classes, and they work pretty well. For earlier Thrift versions, I had to disable discovery of "is-setters", methods Thrift generates to see if a specific property has been explicitly set, but otherwise things worked fine.
I faced the same issue, and I solved it with CustomSerializerFactory.
This approach allows you to ignore some specific field for either for all objects, or for specific types.
public class EntityCustomSerializationFactory extends CustomSerializerFactory {
//ignored fields
private static final Set<String> IGNORED_FIELDS = new HashSet<String>(
Arrays.asList(
"class",
"value",
"some"
)
);
public EntityCustomSerializationFactory() {
super();
}
public EntityCustomSerializationFactory(Config config) {
super(config);
}
#Override
protected void processViews(SerializationConfig config, BeanSerializerBuilder builder) {
super.processViews(config, builder);
//ignore fields only for concrete class
//note, that you can avoid or change this check
if (builder.getBeanDescription().getBeanClass().equals(Entity.class)){
//get original writer
List<BeanPropertyWriter> originalWriters = builder.getProperties();
//create actual writers
List<BeanPropertyWriter> writers = new ArrayList<BeanPropertyWriter>();
for (BeanPropertyWriter writer: originalWriters){
String propName = writer.getName();
//if it isn't ignored field, add to actual writers list
if (!IGNORED_FIELDS.contains(propName)){
writers.add(writer);
}
}
builder.setProperties(writers);
}
}
}
And afterwards you can use it something like the following:
objectMapper.setSerializerFactory(new EntityCustomSerializationFactory());
objectMapper.writeValueAsString(new Entity());//response will be without ignored fields
In case you don't want to pollute your model with annotations, you could use mixins.
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.setMixInAnnotation(Student.class, StudentMixin.class);
mapper.registerModule(simpleModule);
And you want to override id field for example:
public abstract class StudentMixin {
#JsonSerialize(using = StudentIdSerializer.class)
public String id;
}
Do whatever you need with the field:
public class StudentIdSerializer extends JsonSerializer<Integer> {
#Override
public void serialize(Integer integer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(String.valueOf(integer * 2));
}
}
with the help of #JsonView we can decide fields of model classes to serialize which satisfy the minimal criteria ( we have to define the criteria) like we can have one core class with 10 properties but only 5 properties can be serialize which are needful for client only
Define our Views by simply creating following class:
public class Views
{
static class Android{};
static class IOS{};
static class Web{};
}
Annotated model class with views:
public class Demo
{
public Demo()
{
}
#JsonView(Views.IOS.class)
private String iosField;
#JsonView(Views.Android.class)
private String androidField;
#JsonView(Views.Web.class)
private String webField;
// getters/setters
...
..
}
Now we have to write custom json converter by simply extending HttpMessageConverter class from spring as:
public class CustomJacksonConverter implements HttpMessageConverter<Object>
{
public CustomJacksonConverter()
{
super();
//this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.ClientView.class));
this.delegate.getObjectMapper().configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
this.delegate.getObjectMapper().setSerializationInclusion(Include.NON_NULL);
}
// a real message converter that will respond to methods and do the actual work
private MappingJackson2HttpMessageConverter delegate = new MappingJackson2HttpMessageConverter();
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return delegate.canRead(clazz, mediaType);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return delegate.canWrite(clazz, mediaType);
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return delegate.getSupportedMediaTypes();
}
#Override
public Object read(Class<? extends Object> clazz,
HttpInputMessage inputMessage) throws IOException,
HttpMessageNotReadableException {
return delegate.read(clazz, inputMessage);
}
#Override
public void write(Object obj, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException
{
synchronized(this)
{
String userAgent = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("userAgent");
if ( userAgent != null )
{
switch (userAgent)
{
case "IOS" :
this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.IOS.class));
break;
case "Android" :
this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView(Views.Android.class));
break;
case "Web" :
this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( Views.Web.class));
break;
default:
this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
break;
}
}
else
{
// reset to default view
this.delegate.getObjectMapper().setConfig(this.delegate.getObjectMapper().getSerializationConfig().withView( null ));
}
delegate.write(obj, contentType, outputMessage);
}
}
}
Now there is need to tell spring to use this custom json convert by simply putting this in dispatcher-servlet.xml
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean id="jsonConverter" class="com.mactores.org.CustomJacksonConverter" >
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
That's how you will able to decide which fields to get serialize.
Thanx
I have the following class:
public class Message {
private String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}
When converting the instance to JSON using Jackson by default I get:
{"text":"Text"}
I would like to get:
{"message":{"text":"Text"}}
Is there any JAXB / Jackson annotation I can use to achieve my goal?
As a workaround, I can wrap my class with another class:
public class MessageWrapper {
private Message message;
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
}
or a more generic solution:
public class JsonObjectWrapper<T> {
/**
* Using a real map to allow wrapping multiple objects
*/
private Map<String, T> wrappedObjects = new HashMap<String, T>();
public JsonObjectWrapper() {
}
public JsonObjectWrapper(String name, T wrappedObject) {
this.wrappedObjects.put(name, wrappedObject);
}
#JsonAnyGetter
public Map<String, T> any() {
return wrappedObjects;
}
#JsonAnySetter
public void set(String name, T value) {
wrappedObjects.put(name, value);
}
}
Which can be used like so:
Message message = new Message();
message.setText("Text");
JsonObjectWrapper<Message> wrapper = new JsonObjectWrapper<Message>("message", message);
Is there any JAXB / Jackson annotation I can use to achieve my goal?
Thanks.
With Jackson 2.x use can use the following to enable wrapper without adding addition properties in the ObjectMapper
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
#JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
#JsonTypeName(value = "student")
public class Student {
private String name;
private String id;
}
On workaround: you don't absolutely need those getters/setters, so could just have:
public class MessageWrapper {
public Message message;
}
or perhaps add convenience constructor:
public class MessageWrapper {
public Message message;
#JsonCreator
public MessageWrapper(#JsonProperty("message") Message m) {
message = m;
}
}
There is a way to add wrapping too; with 1.9 you can use SerializationConfig.Feature.WRAP_ROOT_ELEMENT and DeserializationConfig.Feature.UNWRAP_ROOT_ELEMENT. And if you want to change the wrapper name (by default it is simply unqualified class name), you can use #JsonRootName annotation
Jackson 2.0 adds further dynamic options via ObjectReader and ObjectWriter, as well as JAX-RS annotations.
It was sad to learn that you must write custom serialization for the simple goal of wrapping a class with a labeled object. After playing around with writing a custom serializer, I concluded that the simplest solution is a generic wrapper. Here's perhaps a more simple implementation of your example above:
public final class JsonObjectWrapper {
private JsonObjectWrapper() {}
public static <E> Map<String, E> withLabel(String label, E wrappedObject) {
HashMap<String, E> map = new HashMap<String, E>();
map.put(label, wrappedObject);
return map;
}
}
Provided you don't mind the json having a capital m in message, then the simplest way to do this is to annotate your class with #JsonTypeInfo.
You would add:
#JsonTypeInfo(include=As.WRAPPER_OBJECT, use=Id.NAME)
public class Message {
// ...
}
to get {"Message":{"text":"Text"}}
A Simpler/Better way to do it:
#JsonRootName(value = "message")
public class Message { ...}
then use
new ObjectMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, true).writeValueAs...
If using spring, then in application.properties file add following:-
spring.jackson.serialization.WRAP_ROOT_VALUE=true
And then use #JsonRootName annotation on any of your class that you wish to serialize. e.g.
#JsonRootName("user")
public class User {
private String name;
private Integer age;
}
I have created a small jackson module that contains a #JsonWrapped annotation, that solves the problem. See here for the code: https://github.com/mwerlitz/jackson-wrapped
Your class would then look like:
public class Message {
#JsonWrapped("message")
private String text;
}
I have a Bean that looks like this
Class Person{
private String name;
private int age
private Properties attributes
public String getName() { return name; }
public int getAge() { return age; }
public Properties getAttributes () { return attributes; }
public void setName(String name) { this.name=name; }
public void setAge(int age) { this.age=age; }
public void setAttributes (Properties attributes) { this.attributes = attributes; }
}
Trying to use Vaadin Forms to allow the editing of the Bean, i assign the Bean to the form using
form.setItemDataSource(new BeanItem<Person>(person));
The form displays correct with the attributes showing “{eye.colour=green,hair.colour=brown}”
But when trying to commit any changes, a Conversion error is thrown in regards to Properties.< Init > not having a string constructor.
How can Vaadin Forms handle Beans with non primitive types properties ?
You'll have to create a custom fieldfactory for the form by extending DefaultFieldFactory.
See https://vaadin.com/book/-/page/components.form.html (half way through the page on how to do this). This will allow you to override the default way form fields are generated. This however doesn't provide an easy way to handle custom fields and conversions like you want.
Luckily though there is a good extension, which I use, to easily create custom fields and property conversions named the custom field plugin.
https://vaadin.com/directory#addon/customfield
With this component you can easily extend existing fields and add custom conversions.
Since there aren't many examples on how to do this out there, here is an example of how the various parts of a custom FieldFactory with a custom field work together.
public class CustomFieldFactory extends DefaultFieldFactory {
public DefaultProfileTableFieldFactory() {
}
#Override
public Field createField(Container container, Object itemId, Object propertyId, Component uiContext) {
if (propertyId.equals(YOURCUSTOMPROP)) {
Select select = new Select();
select.addItem(ITEM1);
select.addItem(ITEM2);
select.addItem(ITEM3);
return new SelectCustomField(select, String.class);
} else {
return super.createField(container, itemId, propertyId, uiContext);
}
}
private static class SelectCustomField extends FieldWrapper<String> {
protected SelectCustomField(Field wrappedField, Class<? extends String> propertyType) {
super(wrappedField, propertyType);
VerticalLayout l = new VerticalLayout();
l.addComponent(wrappedField);
setCompositionRoot(l);
l.setSizeUndefined();
this.setSizeUndefined();
wrappedField.setSizeUndefined();
}
#Override
protected String parse(Object value) throws ConversionException {
// parse value from select to your model
}
#Override
protected Object format(String value) {
// format model from your model to value to be shown
}
}
}