I would like to define my custom serialization strategy (which fields to include), while using Jackson. I know, that I can do it with views/filters, but it introduces very bad thing - using string-representation of field names, which automatically enables problems with auto-refactoring.
How do I force Jackson into serializing only annotated properties and nothing more?
If you disable all auto-detection it should only serialize the properties that you have annotated--whether it be the properties themselves or the getters. Here's a simple example:
private ObjectMapper om;
#Before
public void setUp() throws Exception {
om = new ObjectMapper();
// disable auto detection
om.disable(MapperFeature.AUTO_DETECT_CREATORS,
MapperFeature.AUTO_DETECT_FIELDS,
MapperFeature.AUTO_DETECT_GETTERS,
MapperFeature.AUTO_DETECT_IS_GETTERS);
// if you want to prevent an exception when classes have no annotated properties
om.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
#Test
public void test() throws Exception {
BlahClass blahClass = new BlahClass(5, "email", true);
String s = om.writeValueAsString(blahClass);
System.out.println(s);
}
public static class BlahClass {
#JsonProperty("id")
public Integer id;
#JsonProperty("email")
public String email;
public boolean isThing;
public BlahClass(Integer id, String email, boolean thing) {
this.id = id;
this.email = email;
isThing = thing;
}
}
In case you want to do this without configuring the mapper just for a specific type:
#JsonAutoDetect(
fieldVisibility = Visibility.NONE,
setterVisibility = Visibility.NONE,
getterVisibility = Visibility.NONE,
isGetterVisibility = Visibility.NONE,
creatorVisibility = Visibility.NONE
)
public class BlahClass {
#JsonProperty("id")
private Integer id;
#JsonProperty("email")
private String email;
}
Related
I want to MAP my HTTP request parameter value directly to my DTO USING #JsonProperty on the basis of the variable name not by #JsonProperty value. I am not able to map the value to DTO because it's expecting request value according to the JsonProperty name. Is there anyway to disable #JsonProperty value while using the #RequestBody ?
JSON send by frontend:
{
"userId":"1",
"payMethod":"payMethod"
}
MyDto.class
public class MyDto{
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
//getter setter
}
MyController.class
public class MyController{
#RequestMapping(value = "payment", method = RequestMethod.PUT)
public Integer PaymentUpdate(#RequestBody final MyDto myDto) throws JsonProcessingException {
}
you can do this by using multiple setter method for that DTO method. For example
Payload:
{
"userId":"1",
"payMethod":"payMethod"
}
then
MyDto.class public class MyDto{
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
add one more setter relevant to the required variable name in the DTO class.
#JsonSetter("specifiedName")
void setUserId(String userId){
this.userId=userId
}
void setPayMethod(String payMethod){ // Will work for "BETAALMETHODE" variable name
this.payMethod=payMethod
}
#JsonSetter("payMethod")
void setPayMethod(String payMethod){
this.payMethod=payMethod
}
This will solve your problems and variable payMethod will assign in both the cases.
You can use JacksonMixin during csv parsing:
public abstract class MyDtoMixin {
#JsonProperty(value = user_id, required = true)
private String userId;
#JsonProperty(value = BETAALMETHODE, required = true)
private String payMethod;
}
ObjectMapper mapper = new ObjectMapper(); // or CsvMapper mapper = new CsvMapper();
mapper.addMixInAnnotations(MyDto.class, MyDtoMixin.class);
I want to get the following XML:
<User id="two">
<id>one</id>
</User>
And I try to use Jackson XML mapper for this:
#JacksonXmlRootElement
public class User {
private String id;
private String attributeId;
public User(final String id, final String attributeId) {
this.id = id;
this.attributeId = attributeId;
}
#JacksonXmlProperty(localName = "id")
public String getId() {
return id;
}
#JacksonXmlProperty(localName = "id", isAttribute = true)
public String getAttributeId() {
return attributeId;
}
public static void main(String[] args) throws IOException {
final XmlMapper xmlMapper = new XmlMapper();
final File file = new File("user.xml");
final User user = new User("one", "two");
xmlMapper.writeValue(file, user);
}
}
But all I get is this exception
java.lang.IllegalArgumentException: Conflicting getter definitions for property "id": com.sbconverter.parser.slovoed.User#getId(0 params) vs com.sbconverter.parser.slovoed.User#getAttributeId(0 params)
Can I have same name of the attribute and tag, on one object?
This is a known problem, so you'll need to do extra classes for this case.
Adding a space in front of id at localName (localName = " id") can do the trick, but it's more recommended to make a new bean.
I use Jackson for serialization/deserialization with my Spring Boot project.
I have a DTO object with the following structure,
public class TestDTO implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private UUID certificateId;
#NotNull
private Long orgId;
#NotNull
private CertificateType certificateType;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#Valid
#NotNull
private PublicCertificateDTO publicCertificate;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#Valid
private PrivateCertificateDTO privateCertificate;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private ZonedDateTime expiryDate;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private ZonedDateTime createdDate;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private ZonedDateTime updatedDate;
}
Serialization of this object in my unit tests with the following method,
public static byte[] convertObjectToJsonBytes(TestDTO object)
throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
JavaTimeModule module = new JavaTimeModule();
mapper.registerModule(module);
return mapper.writeValueAsBytes(object);
}
causes fields with WRITE_ONLY access to get ignored (for obvious reasons). So in the serialized object I see null values for publicCertificate and privateCertificate.
I did try setting mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
Is there any other way to ignore these properties for Unit Tests ?
While the solution specified works, it is an overkill for the requirement. You don't need custom serializers if all you want is to override annotations. Jackson has a mixin feature for such trivial requirements
Consider the following simplified POJO:
public class TestDTO
{
public String regularAccessProperty;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public String writeAccessProperty;
}
If you want to override the #JsonProperty annotation, you create another POJO that has a variable with the exact same name (or same getter/setter names):
// mixin class that overrides json access annotation
public class UnitTestDTO
{
#JsonProperty(access = JsonProperty.Access.READ_WRITE)
public String writeAccessProperty;
}
You associate the original POJO and the mixin via a Simplemodule:
simpleModule.setMixInAnnotation(TestDTO.class, UnitTestDTO.class);
Is there any other way to ignore these properties for Unit Tests ?
Solution: In your convertObjectToJsonBytes method, you can use:
mapper.disable(MapperFeature.USE_ANNOTATIONS);
Reference: MapperFeature.USE_ANNOTATIONS
/**
* Feature that determines whether annotation introspection
* is used for configuration; if enabled, configured
* {#link AnnotationIntrospector} will be used: if disabled,
* no annotations are considered.
*<p>
* Feature is enabled by default.
*/
USE_ANNOTATIONS(true),
Note: This will disable all annotations for given ObjectMapper.
Another solution is to override the annotation inspector with a simple custom class. That would be the minimal example:
ObjectMapper mapper = new ObjectMapper().setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
#Override
public JsonProperty.Access findPropertyAccess(Annotated m) {
return null;
}
});
Other solution for Spring Boot #Autowired object mappers:
Use a dedicated class so it's reusable and more readable:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
public class IgnoreReadOnlyFieldsAnnotationInspector extends JacksonAnnotationIntrospector {
#Override
public JsonProperty.Access findPropertyAccess(Annotated m) {
return null;
}
}
Within the test use #BeforeEach (or her older friends)
public class AmazingTest {
#Autowired
ObjectMapper mapper;
#BeforeEach
void beforeAll(){
// need to copy because the autowired mapper in test and the object mapper in code under test are the same instance
mapper = objectMapper.copy();
mapper.setAnnotationIntrospector(new IgnoreReadOnlyFieldsAnnotationInspector());
}
}
This was solved by adding a custom serializer for the JUnit tests.
So for TestDTO I added the serializer as below.
private class TestJsonSerializer extends StdSerializer<TestDTO> {
public TestJsonSerializer() {
this(null);
}
public TestJsonSerializer(Class<TestDTO> t) {
super(t);
}
#Override
public void serialize(TestDTO value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeNumberField("orgId", value.getOrgId());
gen.writeStringField("certificateType", value.getCertificateType().getType());
if (value.getPublicCertificate() != null) {
gen.writeObjectField("publicCertificate", value.getPublicCertificate());
}
if (value.getPrivateCertificate() != null) {
gen.writeObjectField("privateCertificate", value.getPrivateCertificate());
}
gen.writeObjectField("expiryDate", value.getExpiryDate());
gen.writeObjectField("createdDate", value.getCreatedDate());
gen.writeObjectField("updatedDate", value.getUpdatedDate());
gen.writeEndObject();
}
}
I then added,
ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(TestDTO.class, new TestJsonSerializer());
mapper.registerModule(simpleModule);
Similarly added and registered custom serializers for nested objects, publicCertificate and privateCertificate.
Here a simple example
#ToString
#Getter
#Setter
public class Account implements Cloneable {
#JsonProperty(access = Access.WRITE_ONLY)
private Integer accountId;
private String accountType;
private Long balance;
public AccountTest clone() {
AccountTest test = new AccountTest();
test.setAccountId(this.accountId);
test.setAccountType(this.accountType);
test.setBalance(this.balance);
return test;
}
}
#ToString
#Getter
#Setter
public class AccountTest {
private Integer accountId;
private String accountType;
private Long balance;
}
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
try {
Account account = new Account();
account.setAccountId(1999900);
account.setAccountType("Saving");
account.setBalance(2433l);
AccountTest accountTest = account.clone();
System.out.println(account);
byte[] accountBytes = mapper.writeValueAsBytes(account);
System.out.println(new String(accountBytes));
byte[] accountTestBytes = mapper.writeValueAsBytes(accountTest);
System.out.println(new String(accountTestBytes));
} catch (IOException e) { }
}
}
I need to hide some of the fields in the model class in my response object.
I tried to follow this SO answer
but with no luck.
when there are getter and setters for a field then the #JsonIgnore annotation doesn't seem to be working. see the following code snippet for clarifications.
#ApiModel(description = "")
public class APIInfoDTO {
private String id = null;
#JsonIgnore //this field will not be hidden when getters and setters are defined..
private String name = null;
private String status = null;
#JsonIgnore // this "info" field is hidden since there are no getters and setters for this field
private String info = "adncusdvshbdvsbvhdb";
/**
**/
#ApiModelProperty(value = "")
#JsonProperty("id")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
/**
**/
#ApiModelProperty(value = "")
#JsonProperty("name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
**/
#ApiModelProperty(value = "")
#JsonIgnore
public String getDescription() {
return description;
}
#JsonProperty("description")
public void setDescription(String description) {
this.description = description;
}
furthermore this is the code snippet for object mapping
public static APIInfoDTO fromAPIToInfoDTO(API api) {
APIInfoDTO apiInfoDTO = new APIInfoDTO();
apiInfoDTO.setDescription(api.getDescription());
apiInfoDTO.setContext(api.getContext());
apiInfoDTO.setId(api.getUUID());
APIIdentifier apiId = api.getId();
apiInfoDTO.setName(apiId.getApiName());
apiInfoDTO.setVersion(apiId.getVersion());
apiInfoDTO.setProvider(apiId.getProviderName());
apiInfoDTO.setStatus(api.getStatus().toString());
String providerName = api.getId().getProviderName();
apiInfoDTO.setProvider(APIUtil.replaceEmailDomainBack(providerName));
return apiInfoDTO;
}
any helpful answer would be highly appreciated.. Thanks
[UPDATE] The #JsonIgnore works with org.codehaus.jackson:jackson-core-asl:1.8.6 but fails with com.fasterxml.jackson.core:jackson-annotations:2.7.2.. Any idea why???
Add #JsonIgnore Annotation to the getter method as well.
Or Try adding #JsonIgnoreProperties(value={"name"}) at Class level, if this is an option for you
UPDATE
If you have Proper Jackson Library in your classpath (group: 'com.fasterxml.jackson.core', name: 'jackson-core'), #JsonIgnore on your field will work just fine; as long as the getter method you have is a standard getter, you don't have to annotate getter with #JsonIgnore.
If you want to serialize and deserialize your object based only on fields annotations, the Jackson ObjectMapper should be configured to ignore getters and setters method:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibilityChecker(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE));
or
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
It can also be configured at Class level using the #JsonAutoDetect annotation.
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class APIInfoDTO {
// ...
}
I have Pojo object, with getAsJson function to return Json string for this object.
I use JsonProperty to define json properties in this object.
Use writeValueAsString of ObjectMapper to write json string for this object.
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
#JsonIgnoreProperties(ignoreUnknown=true)
public class LogLikeArticleDetail extends BaseObject {
private static final long serialVersionUID = -2018373118257019033L;
#JsonProperty("LikeArticleGUId")
private String likeArticleGUId;
#JsonProperty("UserId")
private String userID;
#JsonProperty("UserName")
private String userName;
#JsonProperty("IP")
private String ip;
#JsonProperty("OS")
private String os;
#JsonProperty("UserAgent")
private String userAgent;
#JsonProperty("WebsiteCode")
private String websiteCode;
#JsonProperty("ArticleId")
private String articleID;
#JsonProperty("ATitle")
private String aTitle;
#JsonProperty("CateAlias")
private String cateAlias;
#JsonProperty("LikeStatus")
private String likeStatus;
#JsonProperty("TimeStamp")
private Date timeStamp;
//get, set....
//....
#JsonIgnore
public String getAsJSON() throws JsonGenerationException, JsonMappingException, IOException{
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(this) ;
}
}
Now, i get result
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
Calendar calendar = Calendar.getInstance();
LogLikeArticleDetail logLikeArticle = new LogLikeArticleDetail("1","2","3","4","5","6","7","8","what thing \"nothing\" show","10","11",calendar.getTime());
System.out.println(logLikeArticle.getAsJSON());
}
But the result's duplicated properties:
{"LikeArticleGUId":"1","UserId":"2","UserName":"3","IP":"4","OS":"5","UserAgent":"6","WebsiteCode":"7","ArticleId":"8","ATitle":"what thing \"nothing\" show","CateAlias":"10","LikeStatus":"11","TimeStamp":1352256727062,"_likeArticleGUId":"1","websiteCode":"7","likeStatus":"11","userID":"2","userName":"3","ip":"4","os":"5","userAgent":"6","articleID":"8","aTitle":"what thing \"nothing\" show","cateAlias":"10","timeStamp":1352256727062}
Show me what's occur in this problem ?
So i do follow:
how to specify jackson to only use fields - preferably globally
I add
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
before
public class LogLikeArticleDetail extends BaseObject
and the result that i want.
So can another solve that in getAsJson() function like:
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibilityChecker(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
return mapper.writeValueAsString(this) ;
Thanks for #Sean Carpenter 's question and #kmb385 answer in link above.
You can also do this per POJO using annotations. Add this string to the top of your class you'd like no auto detection on:
#JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.ANY,
getterVisibility=JsonAutoDetect.Visibility.NONE,
setterVisibility=JsonAutoDetect.Visibility.NONE,
creatorVisibility=JsonAutoDetect.Visibility.NONE)
For example:
#JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.ANY, getterVisibility=JsonAutoDetect.Visibility.NONE,
setterVisibility=JsonAutoDetect.Visibility.NONE, creatorVisibility=JsonAutoDetect.Visibility.NONE)
class Play {
#JsonProperty("Name")
private String name;
#JsonProperty("NickName")
private String nickName;
public Play(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
}
This will return the properties I've defined and not auto-detect the field names and add them to my returned JSON result.
We can also use the #JsonProperty("Name") annotation directly on the getters to avoid duplication.
It is actually not an issue. So, over here what happened was Jackson library was unable to match those fields automatically (there is no assumption of case unification), so you end up with twice the properties you expect.
The simple fix for this issue is to just add annotations to either getters/setters (either is fine.)
#JsonProperty("UserName")
public String getUserName() {
return this.userName;
}
This issue was also raised in Jackson Github repo. You can find the answer in the following link.
https://github.com/FasterXML/jackson-databind/issues/1609