Make #JsonAnySetter work with #Value Lombok - java

I have a json object with a lot of properties (~80 properties) I want to deserialize in a POJO without creating manually all the properties. I was able to do this by using the #JsonAnySetter with a Map property like described here.
Now I want to make this work by making my POJO immutable using Lombok.
I tried this but it does only deserialize the id and code properties. Any idea on how to make it work?
#Value
#Builder
#EqualsAndHashCode
#JsonDeserialize(builder = Product.ProductBuilder.class)
class Product {
#JsonProperty
private String id;
#JsonProperty
private String code;
#Getter(AccessLevel.NONE)
#Builder.Default
#JsonProperty
private Map<String, Optional<Object>> any = new HashMap<>();
#JsonAnyGetter
public Map<String, Optional<Object>> getAny(){
return this.any;
}
#JsonAnySetter
public void setAny(String key, Optional<Object> value){
this.any.put(key, value);
}
}

Update 2021-02-01: Lombok v1.18.16
Starting with v1.18.16, Lombok automatically copies #JsonAnySetter to the #Singular methods in builder. In combination with #Jacksonized you can simply use this code:
#Value
#Jacksonized
#Builder
class Product {
private String id;
private String code;
#JsonAnySetter
#Singular("any")
private Map<String, Object> any;
}
Older Lombok versions
For previous Lombok version, this requires some customization of the generated builder class.
Customizing a lombok builder can be done by simply adding its inner class header to your class. Lombok detects that there is already a builder class and just adds all the things that are not already present. This means you can add your own methods, and if those happen to have the same name than a method that lombok would generate, lombok skips this method.
With this approach, we replace the builder's setter method for "any", adding the required #JsonAnySetter to it. I use a LinkedHashMap as map in case the order is relevant; you can use a regular HashMap if it's not.
Furthermore, we replace the build() method to make sure the map you supply to the constructor is immutable. I use Guava's ImmutableMap here. This will make the created instance an immutable value.
#Value
#Builder
#JsonDeserialize(builder = Product.ProductBuilder.class)
class Product {
#JsonProperty
private String id;
#JsonProperty
private String code;
#Getter(onMethod_ = #JsonAnyGetter)
private Map<String, Object> any;
#JsonPOJOBuilder(withPrefix = "")
public static class ProductBuilder {
#JsonAnySetter
public ProductBuilder any(String anyKey, Object anyValue) {
if (this.any == null) {
this.any = new LinkedHashMap<String, Object>();
}
this.any.put(anyKey, anyValue);
return this;
}
public Product build() {
return new Product(id, code, any == null ? ImmutableMap.of() : ImmutableMap.copyOf(any));
}
}
}

Related

Jackson conflicting setters, even with #JsonIgnore and #JsonProperty

I'm at a complete loss here. I have a class with overloaded setters for a property, and for the life of me cannot get Jackson to pick a correct setter. Stripping out the things not needed from the class, here's the base of what I've got:
class TestDTO {
#Setter(onMethod_={#JsonProperty})
#JsonProperty
protected CustomWrapper wrappedValues = new CustomWrapper();
#JsonIgnore
#XmlTransient
public RecordAudit setWrappedValues(List<WrappedObject> wrappedValues) {
this.customWrapper = new CustomWrapper(wrappedValues);
return this;
}
#JsonIgnore
#XmlTransient
public RecordAudit setWrappedValues(CustomWrapper customWrapper) {
this.customWrapper = customWrapper;
return this;
}
}
I have tried every combination I can think of of #JsonIgnore and #JsonProperty. I've tried just adding #JsonProperty to the #Setter annotation, I've tried only adding #JsonIgnore to the two custom setters, I've tried only #JsonProperty on the field itself, but no matter what I try, I get the following error:
Conflicting setter definitions for property "wrappedValues": ...#setWrappedValues(1 params) vs ...#setWrappedValues(1 params)
Does anyone have any ideas what's going on here? Using Jackson 2.12.4, so I think just #JsonProperty should be all that's needed, but as I mentioned above, that still results in the same error.
This is on JDK 11 if that makes a difference, I'm still new to 11, so am not sure how much that affects this.
You need to mark setter you want to use as com.fasterxml.jackson.annotation.JsonSetter.
class TestDTO {
protected CustomWrapper wrappedValues = new CustomWrapper();
public RecordAudit setWrappedValues(List<WrappedObject> wrappedValues) {
this.customWrapper = new CustomWrapper(wrappedValues);
return this;
}
#JsonSetter
public RecordAudit setWrappedValues(CustomWrapper customWrapper) {
this.customWrapper = customWrapper;
return this;
}
}
P.S. Your #Setter aren't generating anything since there are methods with name setWrappedValues

How to declare a variable or Object of any class type in Java

I am quite new to Java and I am trying to deserialize the JSON using Jackson and I facing some minor issue with regards to declaring the Object/Variable type. I will provide all the codes then explain the issue for easy understanding.
I have an enum that will have the required type values:
public enum IdentifierTypeValues {
Type1,
Type2,
Type3,
//Constructor and Getter of enum values
}
Then for each of these type, I have different classes which will have different input and do a completely different type of process:
public class GenerateType1 {
private String name;
private String age;
//Getter and Setter
//Some required process based on these values
}
public class GenerateType2 {
private String address;
private String city;
private String country;
//Getter and Setter
//Some required process based on these values
}
public class GenerateType3 {
private String firstName;
private String lastName;
private String fullName;
//Getter and Setter
//Some required process based on these values
}
Now I have a wrapper class for these type of classes which will take the type based on enum and typeInfo values. I want the typeInfo values to be any of the class based type something like this:
public class TypeSyntax {
private IdentifierTypeValues indeitiferType;
private GenerateType1 / GenerateType2 / GenerateType3 identifierTypeValues;
//Here the identifierTypeValues can have the values for anytype
//How to declare a variable of any of these class type?
}
This is the class that will be used by my JSON for deserializing. I know I can add a wrapper class of those 3 types and provide that wrapper class as a type class for this. Something like this:
public class WrapperClass{
private GenerateType1 type1;
private GenerateType2 type2;
private GenerateType3 type3;
}
public class TypeSyntax{
private IdentifierTypeValues indeitiferType;
private WrapperClass identifierTypeValues;
//But using this approach will change my JSON structure which I do not want to do.
}
My JSON structure is something like this and I would like to keep it in the same way.
{
"indeitiferType":"Type1",
"identifierTypeValues":{
"name":"Batman",
"age":"2008"
}
}
Is there a way I can declare the variable of multiple type class? or any better approach to handle this by keeping the json format same? I tried searching but I am unable to search what exactly so any help would be really appriciated.
Because the type identifier exists on a different level than the other properties a wrapper class TypeSyntax needed. There are several open feature requests to add wrapping functionality to Jackson e.g. https://github.com/FasterXML/jackson-databind/issues/512
Fortunately polymorphism is supported in Jackson with #JsonTypeInfo and #JsonSubTypes annotations.
Wrapper class should look like:
public class TypeSyntax {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
property = "identifierType")
private GenerateTypeBase identifierTypeValues;
// getters and setters (omitted for brevity)
}
GenerateTypeBase is the common parent class
#JsonSubTypes({
#JsonSubTypes.Type(value = GenerateType1.class, name = "Type1"),
#JsonSubTypes.Type(value = GenerateType2.class, name = "Type2"),
})
public abstract class GenerateTypeBase {
private String name;
private String age;
// getters and setters (omitted for brevity)
}
In this different children classes will instantiated based on the identifierType property.
The children must extend this base class:
public class GenerateType2 extends GenerateTypeBase {
// additional properties
}
In a short test it will be:
#Test
void wrapperTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
GenerateType2 a = new GenerateType2();
a.setName("Foo");
a.setAge("13");
TypeSyntax w = new TypeSyntax();
w.setIdentifierTypeValues(a);
String json = mapper.writeValueAsString(w);
System.out.println(json);
}
and the output:
{
"identifierTypeValues":
{
"name":"Foo",
"age":"13"
},
"identifierType":"Type2"
}
Deserialization
#Test
void wrapperTest() throws IOException {
ObjectMapper mapper = new ObjectMapper();
String input = "{\"identifierTypeValues\": \"name\":\"Foo\",\"age\":\"13\"},\"identifierType\":\"Type2\"}";
TypeSyntax w = mapper.readValue(new StringReader(input), TypeSyntax.class);
assertAll(
() -> assertEquals(GenerateType2.class, o.getIdentifierTypeValues().getClass()),
() -> assertEquals("13", o.getIdentifierTypeValues().getAge())
);
}
If you want more flexibility you can write custom (de)serializer and / or custom resolver. Using custom TypeIdResolver that will possible to convert identifiers to types programmatically instead of using "key-value pairs" in #JsonSubTypes

Problem with ObjectMapper change my fields name

I am trying to make a method to which an object is passed and reads all the fields so that the fields that come to null and are String are given the value of "".
The problem now comes with my class. I have this model:
#Getter
#Setter
#NoArgsConstructor
#ToString
public class AccountModel {
private String noTotCount;
private int nTotal;
private String account;
}
And I did this method:
private ObjectMapper obMapper = new ObjectMapper();
private Object stringNullToEmpty(Object object) {
Class<?> clase = object.getClass();
Map<String, Object> objectMap = obMapper.convertValue(object, new TypeReference<Map<String, Object>>(){});
for (Field field : clase.getDeclaredFields()) {
String fieldName = field.getName();
if(field.getType().equals(String.class) && objectMap.get(fieldName) == null) {
objectMap.put(field.getName(), "a");
}
}
return obMapper.convertValue(objectMap, clase);
}
The error is presented to me when I make the obMapper.convertValue() because he is converting my noTotCount field to nototCount, so when you go into the conditional and try to put(), there is no field in the objectMap that contains the key noTotCount since the key that contains the objectMap is nototCount.
Why does the ObjectMapper convert my noTotCount field to nototCount?
You have encountered a problem with the java bean naming convention, getter names generated by lombok and jackson when you have camelcase properties with a single letter as the first "word". See this question for further details.
In summary, jackson expects the property (getters and setters) as they would be generated by IDEs (e.g. eclipse): getnTotCount, however I guess that lombok generates getNTotCount (I have not de-lomboked your code). This makes jackson fail (reproduced by renaming the getter).
Workaround: Create the getter yourself and prevent lombok from generating it #JsonProperty("nTotCount") public String getNTotCount() or public String getnTotCount()

serialize and deserialize without map name

I have the following Object:
public class Class_a{
private List<class_b> someList;
}
public class Class_b{
private Map<String,String> someMap;
}
My json will look like this:
"someList":[{"someMap":{"strKey1":"strValue1"}},{"someMap":{"strKey2":"strValue2"}}]
Is it possible to serialize a Json that will look like this, without changing my Objects (and I will have the option to deserialize the Object):
"someList":[{"strKey1":"strValue1"},{"strKey2":"strValue2"}]
*I know that if will defined my object like this:
public class Class_a{
private List<Map<Strung,String>> someList;
}
i will get a Json like I want - but I am trying to find more elegant solution then 'list' that contain a 'map'
My project use spring framework and Jackson.
This worked for me I only had to add getters and setters to your classes and I was able to parse with jackson:
#Test
public void t() throws IOException {
String json = "{\"someList\":[{\"someMap\":{\"strKey1\":\"strValue1\"}},{\"someMap\":{\"strKey2\":\"strValue2\"}}]}";
Class_a a = new ObjectMapper().readValue(json, Class_a.class);
System.out.println(a);
}
#Getter
#Setter
public class Class_a{
private List<Class_b> someList;
}
#Getter
#Setter
public class Class_b{
private Map<String,String> someMap;
}
I'm using lombok but that's nothing special, you can create the getters/setters manually too and will work

Want to hide some fields of an object that are being mapped to JSON by Jackson

I have a User class that I want to map to JSON using Jackson.
public class User {
private String name;
private int age;
private int securityCode;
// getters and setters
}
I map this to a JSON string using -
User user = getUserFromDatabase();
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
I don't want to map the securityCode variable. Is there any way of configuring the mapper so that it ignores this field?
I know I can write custom data mappers or use the Streaming API but I would like to know if it possible to do it through configuration?
You have two options:
Jackson works on setters-getters of fields. So, you can just remove getter of field which you want to omit in JSON. ( If you don't need getter at other place.)
Or, you can use the #JsonIgnore annotation of Jackson on getter method of that field and you see there in no such key-value pair in resulted JSON.
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
Adding this here because somebody else may search this again in future, like me. This Answer is an extension to the Accepted Answer
You have two options:
1. Jackson works on setters-getters of fields. So, you can just remove getter of field which you want to omit in JSON. ( If you don't need getter at other place.)
2. Or, you can use the `#JsonIgnore` [annotation of Jackson][1] on getter method of that field and you see there in no such key-value pair in resulted JSON.
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
Actually, newer version of Jackson added READ_ONLY and WRITE_ONLY annotation arguments for JsonProperty. So you could also do something like this.
#JsonProperty(access = Access.WRITE_ONLY)
private String securityCode;
instead of
#JsonIgnore
public int getSecurityCode(){
return securityCode;
}
you also can gather all properties on an annotation class
#JsonIgnoreProperties( { "applications" })
public MyClass ...
String applications;
If you don't want to put annotations on your Pojos you can also use Genson.
Here is how you can exclude a field with it without any annotations (you can also use annotations if you want, but you have the choice).
Genson genson = new Genson.Builder().exclude("securityCode", User.class).create();
// and then
String json = genson.serialize(user);
Field Level:
public class User {
private String name;
private int age;
#JsonIgnore
private int securityCode;
// getters and setters
}
Class Level:
#JsonIgnoreProperties(value = { "securityCode" })
public class User {
private String name;
private int age;
private int securityCode;
}
if you are using GSON you have to mark the field/member declarations as #Expose and use the GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()
Don't forget to mark your sub classes with #Expose otherwise the fields won't show.
I suggest you use this.
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private int securityCode;
This allows you to set the value of securityCode(especially if you use lombok #Setter) and also prevent the field from showing up in the GET request.
I had a similar case where I needed some property to be deserialized (JSON to Object) but not serialized (Object to JSON)
First i went for #JsonIgnore - it did prevent serialization of unwanted property, but failed to de-serialize it too. Trying value attribute didn't help either as it requires some condition.
Finally, working #JsonProperty with access attribute worked like a charm.

Categories

Resources