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()
Related
I have a simple POJO:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class StatusPojo {
private String status;
}
When I de-serialize simple string "asd" (without quotes) like this:
StatusPojo pojo = new ObjectMapper().readValue("asd", StatusPojo.class)
I am getting a StatusPojo object created successfully, with status field's value as "asd", though it is not valid JSON and nowhere has the field name "status" mentioned along.
Why is it behaving like this and how to disable this behavior and have object mapper throw an exception?
Your POJO has #AllArgsConstructor (maybe because of the #Builder) that then generates something like this:
public StatusPojo(String status) {
this.status = status;
}
When ObjectMapper then de-serializes plain string it uses that constructor to create object.
If you added some other property to your pojo, like just:
private String noAsdPlease;
there would be an exception, because ObjectMapper could not find creator, there would not be that above mentioned constructor but with two String params.
At quick glace DeserializationFeature does not have such a feature that disables using one string arg constructor for plain string.
Playing with more fields, removing #Builder & #AllArgsConstructor might resolve your problem but if you cannot change those ther might not be other options.
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));
}
}
}
I am given this escaped JSON
"{\"UniqueId\":[],\"CustomerOffers\":{},\"Success\":false,\"ErrorMessages\":[\"Test Message\"],\"ErrorType\":\"GeneralError\"}"
and I need to convert it to Java object using Jackson.
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'
I created the class:
public class Data {
private List<UUID> UniqueId;
private Map<Integer, List<Integer>> CustomerOffers;
private Boolean Success;
private List<String> ErrorMessages;
private String ErrorType;
// getter, setters
}
Then I created the method to convert it
public class Deserializing {
public void processing(String input) {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
String jsonInString = "\"{\"UniqueId\":[],\"CustomerOffers\":{},\"Success\":false,\"ErrorMessages\":[\"Test Message\"],\"ErrorType\":\"GeneralError\"}\"";
String newJSON = org.apache.commons.lang3.StringEscapeUtils.unescapeJava(jsonInString);
newJSON= newJSON.substring(1, jsonInString.length()-1);
try {
// JSON string to Java object
Data data = mapper.readValue(newJSON, Data.class);
System.out.println(data);
System.out.println("Get Success "+ data.getSuccess()); // return "false" if Data object is public ; null if private
System.out.println("Get UniqueID " + data.getUniqueId()); // return [] if Data object is public ; null if private
} catch (IOException e) {
e.printStackTrace();
}
}
}
Whichever variables in Data class that are set to public, then I will get the corresponding value when I call getters.
Whichever variables in Data class that are set to private, then I will get null when I call getters.
Getters and Setters are always public.
I am wondering, why ObjectMapper can't map the object if it is set to private? I could set it to public, but that is not best practice.
The issue is that Jackson will always assume setSuccess() & getSuccess() will be used for a success field, not Success. JSON field names starting with uppercase letters need to be supported by #JsonProperty. Java has a convention where class members always start with lowercase letters, you can realize that by using this annotation.
When you make fields private, you force Jackson to utilize setters, and the above conflict makes it impossible to properly deserialize the Data object.
Solution is to do;
public class Data {
#JsonProperty("UniqueId")
private List<UUID> uniqueId;
#JsonProperty("CustomerOffers")
private Map<Integer, List<Integer>> customerOffers;
#JsonProperty("Success")
private Boolean success;
#JsonProperty("ErrorMessages")
private List<String> errorMessages;
#JsonProperty("ErrorType")
private String errorType;
// getter&setters
}
Then you will see the values deserialized properly into a Java object;
Get success false
Get uniqueID []
By default, Jackson will only attempt to serialize public fields on the Data class (or whatever class you are trying to serialize/unserialize). However, you may configure ObjectMapper to allow it serialize all fields, regardless of visibility:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
Data data = mapper.readValue(newJSON, Data.class);
See here and here for more information.
In the JavaBeans specification (the universal guide for structuring Java class properties), properties are defined as starting with a lower-case letter (errorMessages); the accessors, because they have a prefix, capitalize it (getErrorMessages). Jackson uses the property names by default, so when you have a method getErrorMessages, it looks for a JSON property with the key errorMessages.
The best approach is to change your JSON; I've seen the styles errorMessages and error_messages but never ErrorMessages. If you can't do that, you can apply #JsonProperty to your properties to tell Jackson to use a different name in the JSON.
Also, another point to note : In this if class Data contains any constructor with arguments and doesn't contain an empty constructor. Then the object mapper will not be able to map the corresponding object. So in this case also it will return null.
We are creating a REST API which is documented using Swagger's #ApiModelProperty annotations. I am writing end-to-end tests for the API, and I need to generate the JSON body for some of the requests. Assume I need to post the following JSON to an endpoint:
{ "name": "dan", "age": "33" }
So far I created a separate class containing all the necessary properties and which can be serialized to JSON using Jackson:
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyPostRequest {
private String name;
private String age;
// getters and fluid setters omitted...
public static MyPostRequest getExample() {
return new MyPostRequest().setName("dan").setAge("33");
}
}
However, we noticed that we already have a very similar class in the codebase which defines the model that the API accepts. In this model class, the example values for each property are already defined in #ApiModelProperty:
#ApiModel(value = "MyAPIModel")
public class MyAPIModel extends AbstractModel {
#ApiModelProperty(required = true, example = "dan")
private String name;
#ApiModelProperty(required = true, example = "33")
private String age;
}
Is there a simple way to generate an instance of MyAPIModel filled with the example values for each property? Note: I need to be able to modify single properties in my end-to-end test before converting to JSON in order to test different edge cases. Therefore it is not sufficient to generate the example JSON directly.
Essentially, can I write a static method getExample() on MyAPIModel (or even better on the base class AbstractModel) which returns an example instance of MyAPIModel as specified in the Swagger annotations?
This does not seem to be possible as of the time of this answer. The closest possibilities I found are:
io.swagger.converter.ModelConverters: The method read() creates Model objects, but the example member in those models is null. The examples are present in the properties member in String form (taken directly from the APIModelParameter annotations).
io.swagger.codegen.examples.ExampleGenerator: The method resolveModelToExample() takes the output from ModelConverters.read(), and generates a Map representing the object with its properties (while also parsing non-string properties such as nested models). This method is used for serializing to JSON. Unfortunately, resolveModelToExample() is private. If it were publicly accessible, code to generate a model default for an annotated Swagger API model class might look like this:
protected <T extends AbstractModel> T getModelExample(Class<T> clazz) {
// Get the swagger model instance including properties list with examples
Map<String,Model> models = ModelConverters.getInstance().read(clazz);
// Parse non-string example values into proper objects, and compile a map of properties representing an example object
ExampleGenerator eg = new ExampleGenerator(models);
Object resolved = eg.resolveModelToExample(clazz.getSimpleName(), null, new HashSet<String>());
if (!(resolved instanceof Map<?,?>)) {
// Model is not an instance of io.swagger.models.ModelImpl, and therefore no example can be resolved
return null;
}
T result = clazz.newInstance();
BeanUtils.populate(result, (Map<?,?>) resolved);
return result;
}
Since in our case all we need are String, boolean and int properties, there is at least the possibility to parse the annotations ourselves in a crazy hackish manner:
protected <T extends MyModelBaseClass> T getModelExample(Class<T> clazz) {
try {
T result = clazz.newInstance();
for(Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(ApiModelProperty.class)) {
String exampleValue = field.getAnnotation(ApiModelProperty.class).example();
if (exampleValue != null) {
boolean accessible = field.isAccessible();
field.setAccessible(true);
setField(result, field, exampleValue);
field.setAccessible(accessible);
}
}
}
return result;
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalArgumentException("Could not create model example", e);
}
}
private <T extends MyModelBaseClass> void setField(T model, Field field, String value) throws IllegalArgumentException, IllegalAccessException {
Class<?> type = field.getType();
LOGGER.info(type.toString());
if (String.class.equals(type)) {
field.set(model, value);
} else if (Boolean.TYPE.equals(type) || Boolean.class.equals(type)) {
field.set(model, Boolean.parseBoolean(value));
} else if (Integer.TYPE.equals(type) || Integer.class.equals(type)) {
field.set(model, Integer.parseInt(value));
}
}
I might open an Issue / PR on Github later to propose adding functionality to Swagger. I am very surprised that nobody else has seemed to request this feature, given that our use case of sending exemplary model instances to the API as a test should be common.
I have some beans, and they model (explicitly) the core data types in a JSon. However, sometimes the Jsons im reading have extra data in them.
Is there a way to annotate/define a Bean in jackson so that it uses explicit field names for some of the fields (the ones I know of, for example), while cramming the extra fields into a map / list ?
Yes there is, assuming you really do want to retain all the extra/unrecognized parameters, then do something like this:
public class MyBean {
private String field1;
private String field2;
private Integer field3;
private Map <String, Object> unknownParameters ;
public MyBean() {
super();
unknownParameters = new HashMap<String, Object>(16);
}
// Getters & Setters here
// Handle unknown deserialization parameters
#JsonAnySetter
protected void handleUnknown(String key, Object value) {
unknownParameters.put(key, value);
}
}
To configure global handling of parameters you can choose to define an implementation of DeserializationProblemHandler and register it globally with the ObjectMapper config.
DeserializationProblemHandler handler = new MyDeserializationProblemHandler();
ObjectMapper.getDeserializationConfig().addHandler(handler);
If you find you really do not care about the unknown parameters, then you can simply turn them off. On a per-class basis with the #JsonIgnoreProperties(ignoreUnknown = true), or globally by configuring ObjectMapper:
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false)