first, sorry about my bad english;
Second, I have the following Code:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class UserAccount implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private List<Venda> vendas;
}
And the following:
public class Venda implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private UserAccount cliente;
}
So, everything is okay and get the json from serialize on this way (when I ask for an UserAccount):
[
{
"id": 1,
"vendas": [
{
"id": 1,
"cliente": 1,
}
]
}
]
And when I ask for a Venda:
[
{
"id": 1,
"cliente": {
"id": 1,
"vendas": [
{
"id": 1,
"cliente": 1
}
]
}
}
]
The problem is, I don't need the "cliente" information on "vendas" in the first case, but in the second one I need the "cliente" information, However I don't want his "vendas", cause I already got it before;
I already trid #JsonIgnore and didn't work for me, what should I do?
PS: I'm working with GSON to get the .Class from JSON, and I get a terrible Exception because sometimes cliente is an Object and sometimes is Integer, so if you guys have another solution that makes cliente and vendas don't change their type, i would to know too. :(
I was able to solve this using Jackson's Mix-in feature. The Mixin feature is a class were you can specify json annotations (on the class, fields and getters/setters) and they apply to the bean/pojo you serialize. Basically, a mixin allows adding annotations at run time and without chaning the bean/pojo source file. You use Jackson's module feature to apply a Mixin at run time.
So I created one mixin that dynamically adds #JsonIgnore annotation to vendas getter method of UserAccount class, and another mixin that adds #JsonIgnore annotation to cliente getter method of Venda class.
Here is the modified UserAccount class:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class UserAccount implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private List<Venda> vendas = new ArrayList<>();
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public List<Venda> getVendas() { return vendas; }
public void setVendas(List<Venda> vendas) { this.vendas = vendas; }
public void addVenda(Venda v) {
this.vendas.add(v);
v.setCliente(this);
}
/**
* a Jackson module that is also a Jackson mixin
* it adds #JsonIgnore annotation to getVendas() method of UserAccount class
*/
public static class FilterVendas extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(UserAccount.class, FilterVendas.class);
}
// implementation of method is irrelevant.
// all we want is the annotation and method's signature
#JsonIgnore
public List<Venda> getVendas() { return null; }
}
Here is the modified Venda class:
public class Venda implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private UserAccount cliente;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public UserAccount getCliente() { return cliente; }
public void setCliente(UserAccount cliente) { this.cliente = cliente; }
/**
* a Jackson module that is also a Jackson mixin
* it adds #JsonIgnore annotation to getCliente() method of Venda class
*/
public static class FilterCliente extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Venda.class, FilterCliente.class);
}
// implementation of method is irrelevant.
// all we want is the annotation and method's signature
#JsonIgnore
public UserAccount getCliente() { return null; }
}
}
and the test method with run time object mapper configuration:
public static void main(String... args) {
Venda v = new Venda();
UserAccount ua = new UserAccount();
v.setId(1L);
ua.setId(1L);
ua.addVenda(v);
try {
ObjectMapper mapper = new ObjectMapper();
System.out.println("UserAccount: (unfiltered)");
System.out.println(mapper.writeValueAsString(ua));
mapper = new ObjectMapper();
// register module at run time to apply filter
mapper.registerModule(new Venda.FilterCliente());
System.out.println("UserAccount: (filtered)");
System.out.println(mapper.writeValueAsString(ua));
mapper = new ObjectMapper();
System.out.println("Venda: (unfiltered)");
System.out.println(mapper.writeValueAsString(v));
mapper = new ObjectMapper();
// register module at run time to apply filter
mapper.registerModule(new UserAccount.FilterVendas());
System.out.println("Venda: (filtered)");
System.out.println(mapper.writeValueAsString(ua));
} catch (Exception e) {
e.printStackTrace();
}
}
output:
UserAccount: (unfiltered)
{"id":1,"vendas":[{"id":1,"cliente":1}]}
UserAccount: (filtered)
{"id":1,"vendas":[{"id":1}]}
Venda: (unfiltered)
{"id":1,"cliente":{"id":1,"vendas":[{"id":1,"cliente":1}]}}
Venda: (filtered)
{"id":1}
Thanks guys, I got the solution by this way:
public class CustomClienteSerializer extends JsonSerializer<UserAccount> {
#Override
public void serialize(UserAccount cliente, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
cliente.setVendas(null);
generator.writeObject(cliente);
}
}
and adding this on my venda class:
#JsonSerialize(using = CustomClienteSerializer.class)
#ManyToOne(fetch = FetchType.EAGER)
private UserAccount cliente;
So... I got the json as I wanted!
Related
I have a base class
#MappedSuperclass
#Data //lombok annotation for getters/setter
public class BaseEntity implements Identifiable<Long> {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Long version;
}
For any derived class Spring Data REST returns JSON without "id" and "version" attributes.
I found 2 solutions:
Projections.
Add getters/setters with another names:
public Long getRevision() {
return version;
}
public void setRevision(Long revision) {
this.version = revision;
}
public Long getIdentifier() {
return id;
}
public void setIdentifier(Long identifier) {
this.id = identifier;
}
Both solutions look like hacks. Does better approach exist?
Showing the ID of the entity is configuring in the RepositoryRestConfigurerAdapter:
#Bean
public RepositoryRestConfigurerAdapter repositoryRestConfigurerAdapter() {
return new RepositoryRestConfigurerAdapter() {
/**
* Exposing ID for some entities
*/
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(MyEntity.class);
super.configureRepositoryRestConfiguration(config);
}
};
}
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 have a custom class with type parameters and it is not being invoked by constraint validator. Is there a way to invoke the validator for type parameters?
SpringController.java
#ResponseBody
#RequestMapping(method = RequestMethod.POST)
public EntityCollection<MyEntity> processData(
#Valid #RequestBody EntityCollection<MyEntity> entityRequestSet
, BindingResult bindingResult) throws Exception {
if(bindingResult.hasErrors())
{
// Problem here is that bindingResult has no errors, even though MyEntity
// has nulls in it. If I use just MyEntity as RequestBody instead of
// EntityCollection<MyEntity>, then the bindingResult has errors in it
// for fields with nulls
MethodParameter parameter = new MethodParameter(this.getClass()
.getMethod(new Object(){}.getClass().getEnclosingMethod().getName(),
EntityCollection.class, BindingResult.class), 0);
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
return null;
}
EntityCollection.java
public class EntityCollection<MyEntity> extends GenericCollectionEntity<MyEntity> {
public EntityCollection() {
super();
}
public EntityCollection(
Collection<MyEntity> entities) {
super(entities);
}
}
GenericCollectionEntity.java
public abstract class GenericCollectionEntity<T> implements Serializable {
private static final long serialVersionUID = 1L;
public GenericCollectionEntity() {
super();
}
public GenericCollectionEntity(Collection<T> entities) {
super();
this.entities = entities;
}
protected Collection<T> entities;
public Collection<T> getEntities() {
return entities;
}
public void setEntities(Collection<T> entities) {
this.entities = entities;
}
}
MyEntity.java
#Entity
public class MyEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Valid
#EmbeddedId
private EntityKey key;
// getters & setters
}
EntityKey.java
#Embeddable
public class EntityKey implements Serializable {
private static final long serialVersionUID = 1L;
#NotNull
#Column(name = "ID")
private String id;
// ommitted other fields
//getters & setters
}
My bad, I couldn't apply my thinking on the basics. I just added #Valid to the field in abstract entity and it started validating the collection.
GenericCollectionEntity.java
#Valid
protected Collection<T> entities;
I have a CrudRepository that is supposed to make a query with an array (findByIn). In my repository tests it works, but when I try to use the query in my service, it doesn't work. Could someone explain why it doesn't work? Here is my setup (excluding some code irrelevant to the question)
Database model:
#Entity
#Table(name="Place")
public class Place implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "placeId", nullable = false)
private Long placeId;
#Column(name = "owner", nullable = false)
private String owner;
public Long getPlaceId() {
return placeId;
}
public void setPlaceId(Long placeId) {
this.placeId = placeId;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
Repository:
#Repository
public interface PlaceRepository extends CrudRepository<Place, Long> {
List<Place> findByPlaceIdIn(Long[] placeId);
}
Service (this is the part not working):
#Service
public class PlaceService {
#Autowired
private PlaceRepository placeRepository;
public List<Place> getPlaces(Long[] placeIds) {
return placeRepository.findByPlaceIdIn(placeIds);
}
}
The problem is that in my service placeRepository.findByPlaceIdIn(placeIds) returns 0 objects if placeIds contains more than one item. If placeIds contains just one item, the query works fine. I tried replacing return placeRepository.findByPlaceIdIn(placeIds) with this piece of code that does the query for every array item one by one (this actually works, but I'd like to get the query work as it should):
ArrayList<Place> places = new ArrayList<Place>();
for (Long placeId : placeIds) {
Long[] id = {placeId};
places.addAll(placeRepository.findByPlaceIdIn(id));
}
return places;
I know that the repository should work, because I have a working test for it:
public class PlaceRepositoryTest {
#Autowired
private PlaceRepository repository;
private static Place place;
private static Place place2;
private static Place otherUsersPlace;
#Test
public void testPlacesfindByPlaceIdIn() {
place = new Place();
place.setOwner(USER_ID);
place2 = new Place();
place2.setOwner(USER_ID);
place = repository.save(place);
place2 = repository.save(place2);
Long[] ids = {place.getPlaceId(), place2.getPlaceId()};
assertEquals(repository.findByPlaceIdIn(ids).size(), 2);
}
}
I also have another repository for other model, which also uses findByIn and it works fine. I can't see any relevant difference between the repositories. I thought it might offer some more details to show the working repository, so I included it below:
Database model:
#Entity
#Table(name="LocalDatabaseRow")
#JsonIgnoreProperties(ignoreUnknown=false)
public class LocalDatabaseRow implements Serializable {
public LocalDatabaseRow() {}
public LocalDatabaseRow(RowType rowType) {
this.rowType = rowType;
}
public enum RowType {
TYPE1,
TYPE2
};
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
#JsonProperty("id")
private Long id;
#JsonProperty("rowType")
#Column(name = "rowType")
private RowType rowType;
public Long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public RowType getRowType() {
return rowType;
}
public void setRowType(RowType rowType) {
this.rowType = rowType;
}
}
Repository:
#Repository
public interface LocalDatabaseRowRepository extends CrudRepository<LocalDatabaseRow, Long> {
List<LocalDatabaseRow> findByRowTypeAndUserIdIn(RowType type, String[] userId);
}
try using a list instead :
findByPlaceIdIn(List placeIdList);
You have a typo in your code (the repository declaration in the service):
#Autowired
private placeRepository placeRepository;
Should be:
#Autowired
private PlaceRepository placeRepository;
I have this Play Model class that I'm trying to modify an object of, and when I want to save it, I get the following exception:
java.lang.RuntimeException: No #javax.persistence.Id field found in class [class models.Contact]
at play.db.ebean.Model._idAccessors(Model.java:39)
at play.db.ebean.Model._getId(Model.java:52)
The class:
#Entity
public class Contact extends Model implements Person {//, Comparable<Contact>{
private Long id;
private Client client;
#Required
private String email;
private String profil_picture;
private Boolean active = new Boolean(true);
private Boolean favorite = new Boolean(false);
#Transient
private Boolean profile_pic_url_init = new Boolean(false);
#Id
#GeneratedValue
public Long getId() {
return id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="client_id")
public Client getClient(){
return client;
}
public void setClient(Client client){
this.client= client;
}
#Column
public Boolean getFavorite() {
return favorite;
}
public void setFavorite(Boolean is_favorite) {
this.favorite = is_favorite;
}
....
}
The code calling the save() method:
List<Contact> contacts_list = current_client.getContacts();
for (Contact c : contacts_list) {
c.setFavorite(false);
c.save();
}
The class actually has an #Id annotation, so any guesses of why this doesn't work? I tried looking it up on google, but couldn't find much about this error. Thanks in advance!
Move #Id annotation to id field instead of its getter.