Default Property Naming Strategy in dropwizard - java

In my dropwizard project I have bean classes, which are used in the Resource class like this :
#GET
#Path("/{id}")
public Response getUser(#PathParam("id") Long id) {
return Response.ok(userDAO.get(id)).build();
}
class User {
private String id;
#JsonProperty("first_name")
private String firstName;
#JsonProperty("last_name")
private String lastName;
}
Is there a way that I can add to dropwizard configuration or in the application class to tell javax to map my bean entity (user) to json using snake_case naming strategy. This will help me avoid using the #JsonProperty("first_name") annotation for every member of the class.
In the absence of the aforementioned annotation, my json looks like this :
{
"firstName": "John",
"lastName": "Doe"
}
I would rather like it to be :
{
"first_name": "John",
"last_name": "Doe"
}

Found the answer on another SO question :
How to make #JsonSnakeCase the default for configuration in Dropwizard
Adding the following line to application does the job !
environment.getObjectMapperFactory()
.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES)

You can annotate the whole entity with #JsonSnakeCase to avoid annotating each and every property.
Otherwise you can configure the global ObjectMapper in your application initialization
objectMapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);

Related

Jackson change property names without copying POJO

Today I have found a problem which I need some advice from you.
I have a model from Frontend application which sends me some Json structure. It uses camelCase.
Then I need to send the same model to another service to trigger some functionality but there is a different Json name convention. It basically uses underscore notation instead of camel case but also there some other random differences so I would not say it is strict underscore.
And here's a problem. When I serialize Json from Frontend, is there a better way to remap those field names instead of creating duplicated model but with different JsonProperty annotation? I would like to avoid this solution because model contains around 15 classes so I would say it's big.
Another solution I was thinking about is JSONATA expression. What do you think?
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ADLayout {
private String firstName;
private LayoutHeader layoutHeader;
private Long projectId;
private List<Worksheet> worksheets;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ADLayoutAnotherService {
#JsonProperty("first_name")
private String firstName;
#JsonProperty("layout_header")
private LayoutHeader layoutHeader;
#JsonProperty("project_id")
private Long projectId;
private List<Worksheet> worksheets;
}
FRONT Model
{
"firstName": "test",
"projectId": 5,
"worksheets": [
{
"sheetUid": "201f1630-c97d-4fec-89b4-7b45b44bcebc",
"sheetHeader": {
"requestedData": "",
"instructions": ""
}
}
]
}
Another service Model
{
"first_name": "test",
"project_id": 5,
"worksheets": [
{
"sheet_uid": "201f1630-c97d-4fec-89b4-7b45b44bcebc",
"sheet_header": {
"requested_data": "",
"INSTRUCTIONS": ""
}
}
]
}
Important thing is that the model sent from Frontend is also stored in database and returned to frontend so #JsonAlias will not work here

Jackson: referencing an object as a property

In my java spring application, I am working with hibernate and jpa, and i use jackson to populate data in DB.
Here is the User class:
#Data
#Entity
public class User{
#Id
#GeneratedValue
Long id;
String username;
String password;
boolean activated;
public User(){}
}
and the second class is:
#Entity
#Data
public class Roles {
#Id
#GeneratedValue
Long id;
#OneToOne
User user;
String role;
public Roles(){}
}
In the class Roles i have a property of User
and then i made a json file to store the data:
[ {"_class" : "com.example.domains.User", "id": 1, "username": "Admin", "password": "123Admin123","activated":true}
,
{"_class" : "com.example.domains.Roles", "id": 1,"user":1, "role": "Admin"}]
Unfortunately, when i run the app it complains with:
.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.example.domains.User: no int/Int-argument constructor/factory method to deserialize from Number value (1)
at [Source: N/A; line: -1, column: -1] (through reference chain: com.example.domains.Roles["user"])
The problem comes from
{"_class" : "com.example.domains.Roles", "id": 1,"user":1, "role": "Admin"}
and when i remove the above line the app works well.
I think, it complains because it cannot make an instance of user.
So, how can i fix it?
Do yourself a favor and stop using your Entities as DTOs!
JPA entities have bidirectional relations, JSON objects don't, I also believe that the responsibilities of an Entity is very different from a DTO, and although joining these responsibilities into a single Java class is possible, in my experience it is a very bad idea.
Here are a couple of reasons
You almost always need more flexibility in the DTO layer, because it is often related to a UI.
You should avoid exposing primary keys from your database to the outside, including your own UI. We always generate an additional uniqueId (UUID) for every publicly exposed Entity, the primary key stays in the DB and is only used for joins.
You often need multiple views of the same Entity. Or a single view of multiple entities.
If you need to add a new entity to a relation with an existing, you will need find the existing one in the database, so posting the new and old object as a single JSON structure has no advantage. You just need the uniqueId of the existing, and then new.
A lot of the problems developers have with JPA, specifically with regards to merging comes from the fact that they receive a detached entity after their json has been deserialized. But this entity typically doesn't have the OneToMany relations (and if it does, it's the parent which has a relation to the child in JSON, but in JPA it is the child's reference to the parent which constitutes the relationship). In most cases you will always need to load the existing version of the entity from the database, and then copy the changes from your DTO into the entity.
I have worked extensively with JPA since 2009, and I know most corner cases of detachment and merging, and have no problem using an Entity as a DTO, but I have seen the confusion and types of errors that occur when you hand such code over to some one who is not intimately familiar with JPA. The few lines you need for a DTO (especially since you already use Lombok), are so simple and allows you much more flexibility, than trying to save a few files and breaking the separation of concerns.
Jackson provide ObjectIdResolver interface for resolving the objects from ids during de-serialization.
In your case you want to resolve the id based from the JPA/hibernate. So you need to implement a custom resolver to resolve id by calling the JPA/hierbate entity manager.
At high level below are the steps:
Implement a custom ObjectIdResolver say JPAEntityResolver (you may extends from SimpleObjectIdResolver). During resolving object it will call JPA entity manager class to find entity by given id and scope(see. ObjectIdResolver#resolveId java docs)
//Example only;
#Component
#Scope("prototype") // must not be a singleton component as it has state
public class JPAEntityResolver extends SimpleObjectIdResolver {
//This would be JPA based object repository or you can EntityManager instance directly.
private PersistentObjectRepository objectRepository;
#Autowired
public JPAEntityResolver (PersistentObjectRepository objectRepository) {
this.objectRepository = objectRepository;
}
#Override
public void bindItem(IdKey id, Object pojo) {
super.bindItem(id, pojo);
}
#Override
public Object resolveId(IdKey id) {
Object resolved = super.resolveId(id);
if (resolved == null) {
resolved = _tryToLoadFromSource(id);
bindItem(id, resolved);
}
return resolved;
}
private Object _tryToLoadFromSource(IdKey idKey) {
requireNonNull(idKey.scope, "global scope does not supported");
String id = (String) idKey.key;
Class<?> poType = idKey.scope;
return objectRepository.getById(id, poType);
}
#Override
public ObjectIdResolver newForDeserialization(Object context) {
return new JPAEntityResolver(objectRepository);
}
#Override
public boolean canUseFor(ObjectIdResolver resolverType) {
return resolverType.getClass() == JPAEntityResolver.class;
}
}
Tell Jackson to use a custom id resolver for a class, by using annotation JsonIdentityInfo(resolver = JPAEntityResolver.class). For e.g.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = User.class,
resolver = JPAObjectIdResolver.class)
public class User { ... }
JPAObjectIdResolver is a custom implementation and will have dependency on other resources( JPA Entity Manager) which might not be known to Jackson. So Jackson need help to instantiate resolver object. For this purpose, you need to supply a custom HandlerInstantiator to ObjectMapper instance. (In my case I was using spring so I asked spring to create instance of JPAObjectIdResolver by using autowiring)
Now de-serialization should work as expected.
Hope this helps.
I have changed the json file to :
[
{"_class" : "com.example.domains.User",
"id": 1,
"username": "Admin",
"password": "123Admin123",
"activated":true
},
{
"_class" : "com.example.domains.Roles",
"id": 1,
"user":{"_class" : "com.example.domains.User",
"id": 1,
"username": "Admin",
"password": "123Admin123",
"activated":true
},
"role": "Admin"
}
]
But i still think, the best ways is using a foreign key to user record.
Any solution is welcomed
If your bean doesn't strictly adhere to the JavaBeans format, Jackson has difficulties.
It's best to create an explicit #JsonCreator constructor for your JSON model bean, e.g.
class User {
...
#JsonCreator
public User(#JsonProperty("name") String name,
#JsonProperty("age") int age) {
this.name = name;
this.age = age;
}
..
}
1-1 mapping of fields works well , but when it comes to complex object mapping , better to use some API.
You can use Dozer Mapping or Mapstruct to map Object instances.
Dozer has spring integration also.
You could specify non default constructors and then use a custom deserialiser.
Something like this should work (it has not been tested).
public class RolesDeserializer extends StdDeserializer<Roles> {
public RolesDeserializer() {
this(null);
}
public RolesDeserializer(Class<?> c) {
super(c);
}
#Override
public Roles deserialize(JsonParser jp, DeserializationContext dsctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
long id = ((LongNode) node.get("id")).longValue();
String roleName = node.get("role").asText();
long userId = ((LongNode) node.get("user")).longValue();
//Based on the userId you need to search the user and build the user object properly
User user = new User(userId, ....);
return new Roles(id, roleName, user);
}
}
Then you need to register your new deserialiser (1) or use the #JsonDeserialize annotation (2)
(1)
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new RolesDeserializer());
mapper.registerModule(module);
Roles deserializedRol = mapper.readValue(yourjson, Roles.class);
(2)
#JsonDeserialize(using = RolesDeserializer.class)
#Entity
#Data
public class Roles {
...
}
Roles deserializedRol = new ObjectMapper().readValue(yourjson, Roles.class);
public class Roles {
#Id
#GeneratedValue
Long id;
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
#JsonIdentityReference(alwaysAsId = true)
#OneToOne
User user;
String role;
public Roles(){}
}

How to map json to object using spring boot [duplicate]

This question already has answers here:
Annotation for binding a json field to a field in POJO with a different name
(2 answers)
Closed 6 years ago.
Hello I'd like to know how I could mapp my json message to object in java when using spring boot.
Let's say I'm getting json like
{
"customerId": 2,
"firstName": "Jan",
"lastName": "Nowak",
"town": "Katowice"
}
and I'd like to make it entity in my java program:
and for whatever reason I dont want to have match on field names
public class Customer {
//Something like #Map("customerId")
private long OMG;
//Something like #Map("firstName")
private String WTF;
//Something like #Map("lastName")
private String LOL;
//Something like #Map("town")
private String YOLO;
I cannot find what annotation I should use, Not using jackson, just built in spring boot converter??
Spring boot comes with Jackson out-of-the-box.
You can use #RequestBody Spring MVC annotation to un-marshall json string to Java object... something like this.
#RestController
public class CustomerController {
//#Autowired CustomerService customerService;
#RequestMapping(path="/customers", method= RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public Customer postCustomer(#RequestBody Customer customer){
//return customerService.createCustomer(customer);
}
}
Annotate your entities member elements with #JsonProperty with corresponding json field names.
public class Customer {
#JsonProperty("customerId")
private long OMG;
#JsonProperty("firstName")
private String WTF;
#JsonProperty("lastName")
private String LOL;
#JsonProperty("town")
private String YOLO;
}
Spring Boot does grouping dependencies, glue and default configuration. It is not a serialization api. You should use Jackson to perform your need
You shoud map your class such as :
public class Customer {
#JsonProperty("customerId")
private long OMG;
#JsonProperty("firstName")
private String WTF;
#JsonProperty("lastName")
private String LOL;
#JsonProperty("town")
private String YOLO;
....
}
From JsonProperty annotation Javadoc :
Marker annotation that can be used to define a non-static method as a
"setter" or "getter" for a logical property (depending on its
signature), or non-static object field to be used (serialized,
deserialized) as a logical property.
Default value ("") indicates that the field name is used as the
property name without any modifications, but it can be specified to
non-empty value to specify different name. Property name refers to
name used externally, as the field name in JSON objects.

Ignore JsonIgnore in Elasticsearch

I am developing an application which uses Spring-boot, a relational database and Elasticsearch.
I use JSON serialization at 2 differents places in the code:
In the response of the REST API.
When the code interacts with Elasticsearch.
There are some properties that I need in Elasticsearch but that I want to hide to the application user (e.g. internal ids coming from the relational database).
Here is an example of entity :
#Document
public class MyElasticsearchEntity {
#Id
private Long id; //I want to hide this to the user.
private String name;
private String description;
}
Problem : When the object it persisted in Elasticsearch, it gets serialized as JSON. Hence, fields with #JsonIgnore are ignored when serialized to Elasticsearch.
Up to now, I found 2 unsatisfying solutions :
Solution 1 : Use #JsonProperty like this :
#Id
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Long id;
The id gets written in Elasticsearch and is nullified in the JSON response :
{
"id" : null,
"name" : "abc",
"description" : null
}
So it works but the application user still sees that this property exists. This is messy.
Solution 2 : Cutomize the object mapper to ignore null values
Spring-boot has a built-in option for that :
spring.jackson.serialization-inclusion=NON_NULL
Problem : it suppresses all non-null properties, not only those that I want to ignore. Suppose that the field description of the previous entity is empty, the JSON response will be :
{
"name" : "abc"
}
And this is problematic for the UI.
So is there a way to ignore such field only in the JSON response?
You could use Jackson JsonView for your purpose. You can define one view which will be used to serialize pojo for the application user :
Create the views as class, one public and one private:
class Views {
static class Public { }
static class Private extends Public { }
}
Then uses the view in your Pojo as an annotation:
#Id
#JsonView(Views.Private.class) String name;
private Long id;
#JsonView(Views.Public.class) String name;
private String publicField;
and then serialize your pojo for the application user using the view:
objectMapper.writeValueUsingView(out, beanInstance, Views.Public.class);
This is one example of many others on how view can fit your question. Eg. you can use too objectMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false); to exclude field without view annotation and remove the Private view.

How to create and connect related resources using Spring Data REST repositories?

I have a simple proof-of-concept demo using Spring Data REST / RestRepository architecture. My two entities are :
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="Address")
public class Address implements Serializable {
public Address() {}
#Column(name="ID", nullable=false, unique=true)
#Id
#GeneratedValue(generator="CUSTOMER_ADDRESSES_ADDRESS_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="CUSTOMER_ADDRESSES_ADDRESS_ID_GENERATOR", strategy="native")
private int ID;
#RestResource(exported = false)
#ManyToOne(targetEntity=domain.location.CityStateZip.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.PERSIST})
#JoinColumns({ #JoinColumn(name="CityStateZipID", referencedColumnName="ID", nullable=false) })
private domain.location.CityStateZip cityStateZip;
#Column(name="StreetNo", nullable=true)
private int streetNo;
#Column(name="StreetName", nullable=false, length=40)
private String streetName;
<setters and getters ommitted>
}
and for CityStateZip:
#Entity
public class CityStateZip {
public CityStateZip() {}
#Column(name="ID", nullable=false, unique=true)
#Id
#GeneratedValue(generator="CUSTOMER_ADDRESSES_CITYSTATEZIP_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="CUSTOMER_ADDRESSES_CITYSTATEZIP_ID_GENERATOR", strategy="native")
private int ID;
#Column(name="ZipCode", nullable=false, length=10)
private String zipCode;
#Column(name="City", nullable=false, length=24)
private String city;
#Column(name="StateProv", nullable=false, length=2)
private String stateProv;
}
with repositories:
#RepositoryRestResource(collectionResourceRel = "addr", path = "addr")
public interface AddressRepository extends JpaRepository<Address, Integer> {
List<Address> findByStreetNoAndStreetNameStartingWithIgnoreCase(#Param("stNumber") Integer streetNo, #Param("street") String streetName);
List<Address> findByStreetNameStartingWithIgnoreCase(#Param("street") String streetName);
List<Address> findByStreetNo(#Param("streetNo") Integer strNo);
}
and:
// #RepositoryRestResource(collectionResourceRel = "zip", path = "zip", exported = false)
#RepositoryRestResource(collectionResourceRel = "zip", path = "zip")
public interface CityStateZipRepository extends JpaRepository<CityStateZip, Integer> {
List<CityStateZip> findByZipCode(#Param("zipCode") String zipCode);
List<CityStateZip> findByStateProv(#Param("stateProv") String stateProv);
List<CityStateZip> findByCityAndStateProv(#Param("city") String city, #Param("state") String state);
}
and main() code of
#Configuration
#EnableJpaRepositories
#Import(RepositoryRestMvcConfiguration.class)
#EnableAutoConfiguration
// #EnableTransactionManagement
#PropertySource(value = { "file:/etc/domain.location/application.properties" })
#ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
with this code, I can save a CSZ by POSTing this JSON to http://example.com:8080/zip:
{ "zipCode" : "28899" , "city" : "Ada", "stateProv" : "NC" }
but if I try to save an Address by POSTing the JSON to …/add:
{ "streetNo" : "985" , "streetName" : "Bellingham", "plus4Zip" : 2212, "cityStateZip" : { "zipCode" : "28115" , "city" : "Mooresville", "stateProv" : "NC" } }
I get the error
{
"cause": {
"cause": {
"cause": null,
"message": "Template must not be null or empty!"
},
"message": "Template must not be null or empty! (through reference chain: domain.location.Address[\"cityStateZip\"])"
},
"message": "Could not read JSON: Template must not be null or empty! (through reference chain: domain.location.Address[\"cityStateZip\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Template must not be null or empty! (through reference chain: domain.location.Address[\"cityStateZip\"])"
}
Now if I change CityStateZipRepository to include export=false in the annotation, I can then save the Address and CSZ to the database. But at that time, …/zip is no longer exposed on the interface, AND doing GET …/addr or …/addr/{id} causes this error:
{
"timestamp": 1417728145384,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.http.converter.HttpMessageNotWritableException",
"message": "Could not write JSON: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.hateoas.PagedResources[\"_embedded\"]->java.util.UnmodifiableMap[\"addr\"]->java.util.ArrayList[0]->org.springframework.hateoas.Resource[\"content\"]->domain.location.Address[\"cityStateZip\"]->domain.location.CityStateZip_$$_jvst4e0_0[\"handler\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.hateoas.PagedResources[\"_embedded\"]->java.util.UnmodifiableMap[\"addr\"]->java.util.ArrayList[0]->org.springframework.hateoas.Resource[\"content\"]->domain.location.Address[\"cityStateZip\"]->domain.location.CityStateZip_$$_jvst4e0_0[\"handler\"])",
"path": "/addr"
}
Isa there a way to set up this model to be able to POST and GET from this database? Also, the JSON passed to Address will save a new instance of CityStateZip - what format will allow us to reference an existing CityStateZip element?
Thanks for any help you can provide - this has been driving us crazy for days now.
There's a mismatch in how you use the objects and how you've set them up in your domain-objects/repository structure. Here's what you effectively do:
In contrast to what you asked in the original headline of your question ("GETing and POSTing nested entities in RestRepository"), on the HTTP level, Address and CityZipState are not embedded, they're siblings. By providing repositories for both Address and CityStateZip you basically elevate the concepts to aggregate roots, which Spring Data REST translates into dedicated HTTP resources. In your HTTP communication you now treat CityStateZip like a value object, as you don't refer to it by its identifier which - in a REST context - is the URI you get returned in the Location header of the first request.
So if you want to keep the domain types / repositories structure as is, you need to change your HTTP interaction as follows:
POST $zipsUri { "zipCode" : "28899" , "city" : "Ada", "stateProv" : "NC" }
201 Created
Location: $createdZipUri
Now you can use the returned URI to create the Address:
POST $addressesUri { "streetNo" : "985" , "streetName" : "Bellingham", "plus4Zip" : 2212, "cityStateZip" : $createdZipUri }
201 Created
Location: $createdAddressUri
So what you basically express is: "Please create an address with these details but refer to this CityZipState."
Another option would be to change your domain types / repositories structure to either not expose the repository or turn CityStateZip into a value object. The error you run into is caused by Jackson not being able to marshal Hibernate proxies out of the box. Make sure you have the Jackson Hibernate module on the classpath. Spring Data REST will automatically register it for you then. You might wanna switch to eager loading for the cityStateZip property in Address as it effectively removeds the need to create a proxy at all and the target object is basically a set of primitives so that there's not big price to pay for the additional join.
Assuming the parent entity already exists (in this case, CityStateZip), create the address by referencing the CityStateZip's URI:
{ "streetNo" : "985" , "streetName" : "Bellingham", "plus4Zip" : 2212, "cityStateZip" : "http://example.com:8080/zip/idOfZip" }

Categories

Resources