I am making an external call via RestTemplate as follows:
ResponseEntity<Response> response = template.exchange("some.endpoint.com", HttpMethod.POST, request, MyClass.class);
The API returns a boolean value in String format as follows: ("0" or "1")
{
"some_lengthy_key_name" : "1"
}
I am trying to map this response to the following class.
#Getter
#JsonDeserialize(builder = MyClass.MyClassBuilder.class)
#Builder
public class MyClass{
#JsonProperty("some_lengthy_key_name")
private final boolean isValid;
}
It looks like Jackson doesn't entertain this and throws following error (understandable):
Can not deserialize value of type boolean from String "1" only "true"
or "false" recognized.
I don't want to go down the route of capturing it as a String and then modifying value after.
Instead prefer to go for the option of getting a custom deserialization going and went for the following:
public class Deserializer extends JsonDeserializer<Boolean> {
#Override
public Boolean deserialize(JsonParser parser, DeserializationContext context) throws IOException {
return !"0".equals(parser.getText());
}
}
I've now annotated the field in MyClass as follows:
#Getter
#JsonDeserialize(builder = MyClass.MyClassBuilder.class)
#Builder
public class MyClass{
#JsonDeserialize(using = Deserializer.class)
#JsonProperty("some_lengthy_key_name")
private final boolean isValid
}
But unfortunately this is not working either and throwing same error.
Could I get some advice as to what I am doing wrong with this custom deserialization? Thanks.
You don't need any custom deserializer for this, just override the property with custom setter method and then annotated that method with #JsonProperty. Another note jackson uses getters and setters for serialization and deserialization so you cannot declare variables as final
#Getter
#Setter
class MyClass{
private boolean isValid;
#JsonProperty("some_lengthy_key_name")
public void setIsValid(String value) {
isValid = "1".equals(value);
}
}
There is another way in jackson for deserializing using constructor to prevent immutability of object, check this for more information
#Getter
#Setter
#JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public class MyClass{
private final boolean isValid;
public MyClass(#JsonProperty("some_lengthy_key_name") String name) {
this.isValid = "1".equals(name);
}
You can not use final, and use a Boolean type instead of boolean
#Getter
#Setter
public class MyClass{
#JsonDeserialize(using = Deserializer.class)
#JsonProperty("some_lengthy_key_name")
private Boolean isValid; //<= You did not use final, and use Boolean type instead of boolean
}
Related
I have a following endpoint:
#GetMapping("/{campaign}")
#SneakyThrows
public S3StringHolder downloadRawFactDataS3(#PathVariable Integer campaign) {
String selectDataQuery = new RawFactSelectTemplate(campaign).translate().getSqlQuery();
//todo: find some way to do it on object mapper level
return new StringHolder(service.downloadDataFilledTemplate(campaign, selectDataQuery));
}
StringHolderClass
#NoArgsConstructor
#AllArgsConstructor
#Data
public class StringHolder {
private String fileS3Id;
}
I use StringHolder only because i need to return here not just a simple string with service.downloadDataFilledTemplate(campaignId, selectDataQuery) method call result, but a json which will look like:
{
fileS3Id: "hereSomeText"
}
Is there some possible good-looking ways to avoid usage of StringHolder class still preserving the structure of output JSON?
To avoid wrapping your String in a Class you could make your controller return a Map<String, String> and then return:
return Collections.singletonMap("key", myText)
I have superclass:
#Getter
#Setter
public abstract class AbstractDto {
#NotNull
protected String num;
}
And subclass:
#Getter
#Setter
public class SomeDto extends AbstractDto {
#NotNull
private Long id;
}
When I serialize it, all is ok:
{"num":"5600511164","id":22}
But when I try to deserialize, I get all fields as null in SomeDto.
I use this configuration
#Bean
public ObjectMapper objectMapper (Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
I think problem is in use superclass, but I don't know how to fix it. Without superclass always work fine.
UPD.
Serialize like this:
protected String marshall(Object message) throws JsonProcessingException {
return this.objectMapper.writeValueAsString(message);
}
Deserialize like this:
protected T unmarshall(byte[] body) throws IOException {
return objectMapper.readValue(body, resolveType());
}
resolveType() return me SomeDto.class
UPD. Solved.
Problem was in body, it's not only {"num":"5600511164","id":22}, it contains another info.
#AllArgsConstructor
#Getter
public enum MemberType {
INTERN("name_intern", 1),
EMPLOYEE("name_employee", 10);
private String name;
private int workingMonth;
}
Here is my enum. I want to convert Enum class to JSON string with some constraint.
I want to MemberType has no dependency with Jackson
I want to convert MemberType.INTERN to {id:INTERN, name:"name_intern", workingMonth:10}.
I have lots of Enums want to convert like above. And Their number of property is different each other.
I want resolve this problem through just one global configuration.
I don't want to use explicit java reflection.
Is there a solution that meets the above constraints?
You can use #JsonFormat annotation like this:
#JsonFormat(shape=JsonFormat.Shape.OBJECT)
public enum MemberType { ... }
or you can use #JsonValue annotation like this:
public enum MemberType {
[...]
#JsonValue
public String getName() {
return name;
}
}
or maybe a CustomSerializer for Enum, you can find more details here.
If you implement JsonSerializer,you can custom serialization.
An example is shown below.
#JsonComponent
public final class MediaTypeJsonComponent {
public static class Serializer extends JsonSerializer<MemberType> {
#Override
public void serialize(MemberType value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeStringField("id", value.name());
gen.writeNumberField("workingMonth", value.getWorkingMonth());
gen.writeStringField("name", value.getName());
gen.writeEndObject();
}
}
//
// If you need,write code.
//public static class Deserializer extends JsonDeserializer<Customer> {
//}
}
Another way is to implement JsonSerialize.
If you want more information, you should refer to:
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/jackson/JsonComponent.html
https://www.baeldung.com/jackson-custom-serialization
How do I use a custom Serializer with Jackson?
https://www.baeldung.com/jackson-serialize-enums
Using JAX-RS (GlassFish 4) and Jackson as serializer, I encountered a problem during deserialization of POJO properties of type Double (same with Integer).
Lets say I have simple POJO (excluding bunch of other fieds, getters and setters):
public class MapConstraints {
private Double zoomLatitude;
private Double zoomLongitude;
}
When a user send request to API in format { "zoomLatitude": "14.45", "zoomLongitude": ""}, value of zoomLatitude is set to 14.45, but value of zoomLongitude is set to 0.
I expect value of zoomLongitude to be null if no value is presented. I tried configure ObjectMapper with JsonInclude.Include.NON_EMPTY but without success.
Same results with genson.
You can annotate your POJOs with #JsonInclude to indicate which values will be (de)serialized:
#JsonInclude(Include.NON_EMPTY)
private Double zoomLongitude
In Jackson 2.6, the following values are available:
ALWAYS
NON_ABSENT
NON_DEFAULT
NON_EMPTY
NON_NULL
USE_DEFAULTS
However, if #JsonInclude values don't fit your needs, you can create a custom JsonDeserializer:
public class CustomDeserializer extends JsonDeserializer<Double> {
#Override
public Double deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String text = jp.getText();
if (text == null || text.isEmpty()) {
return null;
} else {
return Double.valueOf(text);
}
}
}
And then just annotate your fields with #JsonDeserialize indicating the deserializer you want to use:
public class MapConstraints {
#JsonDeserialize(using = CustomDeserializer.class)
private Double zoomLatitude;
#JsonDeserialize(using = CustomDeserializer.class)
private Double zoomLongitude;
}
Use this annotation on the POJO:
#JsonInclude(Include.ALWAYS)
i have an class with the following annotations:
class A {
public Map<String,List<String>> references;
#JsonProperty
public Map<String,List<String>> getReferences() {
...
}
#JsonIgnore
public void setReferences(Map<String,List<String>>) {
}
...
}
}
What I try is to ignore the json on deserialization. But it doesn't work. Always when JSON String arrives the Jackson lib fill the references attribute. If I use only the #JsonIgnore annotation the getter doesn't work. Are there any solutions for this problem?
Thanks
I think there are two key pieces that should enable you to have "read-only collections" as desired. First, in addition to ignoring the setter, ensure that your field is also marked with #JsonIgnore:
class A {
#JsonIgnore
public Map<String,List<String>> references;
#JsonProperty
public Map<String,List<String>> getReferences() { ... }
#JsonIgnore
public void setReferences(Map<String,List<String>>) { ... }
}
Second, in order to prevent the getters from being used as setters, disable the USE_GETTERS_AS_SETTERS feature:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.USE_GETTERS_AS_SETTERS);
As of Jackson 2.6, there is a new and improved way to define read-only and write-only properties, using JsonProperty#access() annotation. This is recommended over use of separate JsonIgnore and JsonProperty annotations.
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Map<String,List<String>> references;
You have to make sure there is #JsonIgnore annotation on the field level as well as on the setter, and getter annotated with #JsonProperty.
public class Echo {
#Null
#JsonIgnore
private String doNotDeserialise;
private String echo;
#JsonProperty
public String getDoNotDeserialise() {
return doNotDeserialise;
}
#JsonIgnore
public void setDoNotDeserialise(String doNotDeserialise) {
this.doNotDeserialise = doNotDeserialise;
}
public String getEcho() {
return echo;
}
public void setEcho(String echo) {
this.echo = echo;
}
}
#Controller
public class EchoController {
#ResponseBody
#RequestMapping(value = "/echo", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public Echo echo(#RequestBody #Valid Echo echo) {
if (StringUtils.isEmpty(echo.getDoNotDeserialise())) {
echo.setDoNotDeserialise("Value is set by the server, not by the client!");
}
return echo;
}
}
If you submit a JSON request with a “doNotDeserialise” value set to something, when JSON is deserialised to an object it will be set to null (if not I put a validation constraint on the field so it will error out)
If you set the “doNotDeserialise” value to something on the server then it will be correctly serialised to JSON and pushed to the client
I used #JsonIgnore on my getter and it didn't work and I couldn't configure the mapper (I was using Jackson Jaxrs providers). This worked for me:
#JsonIgnoreProperties(ignoreUnknown = true, value = { "actorsAsString",
"writersAsString", "directorsAsString", "genresAsString" })
I can only think of a non-jackson solution, to use a base class that does not have references for the mapping and then cast to the actual class:
// expect a B on an incoming request
class B {
// ...
}
// after the data is read, cast to A which will have empty references
class A extends B {
public Map<String,List<String>> references;
}
Why do you even send the References if you don't want them?
Or is the incoming data out of your hands and you just want to avoid the mapping exception telling you that jackson cannot find a property to set for incoming references? For that we use a base class which all of our Json model classes inherit:
public abstract class JsonObject {
#JsonAnySetter
public void handleUnknown(String key, Object value) {
// for us we log an error if we can't map but you can skip that
Log log = LogFactory.getLog(String.class);
log.error("Error mapping object of type: " + this.getClass().getName());
log.error("Could not map key: \"" + key + "\" and value: \"" + "\"" + value.toString() + "\"");
}
Then in the POJO you add #JsonIgnoreProperties so that incoming properties will get forwarded to handleUnknown()
#JsonIgnoreProperties
class A extends JsonObject {
// no references if you don't need them
}
edit
This SO Thread describes how to use Mixins. This might be the solution, if you want to keep your structure exactly as it is, but I have not tried it.