Jackson change property names without copying POJO - java

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

Related

How do I edit the ResponseEntity of a Get-Request in Java Spring?

I have a small Java Springboot backend and a mySQL database. The data class for the objects to be saved in the database looks as follows:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String category;
private LocalDate expirationDate;
// getters, setters ...
The GET method within the controller class looks like this:
#GetMapping("/products")
public ResponseEntity<Iterable<Product>> getProducts() {
return ResponseEntity.ok(productRepository.findAll());
}
Now the json I get back when posting a GET-request formats the expirationDate property as int[], like this:
"expirationDate": [
2023,
1,
22
]
How do I manage to format this while creating the json for it to look like yyyy-mm-dd, as this is the format I also use to POST it. Also, as I'm writing my frontend in Blazor (C#), I need a format that gets easily translated into a DateOnly property in the C# data class.
change the controller to return List of products as follows:
#GetMapping("/products")
public ResponseEntity<List<Product>> getProducts() {
return ResponseEntity.ok(productRepository.findAll());
}
Also, I recommend to use lombok and put #Data anotation on your Entity.
I just reproduce yours and you will got the data like this:
[
{
"id": 1,
"name": "Iphone",
"category": "apple",
"expirationDate": "2022-12-23"
},
{
"id": 2,
"name": "Samsung",
"category": "samsung",
"expirationDate": "2022-12-23"
}
]
Incase you need, this is your post mapping:
#PostMapping("/products")
public Product createProduct(#RequestBody Product product) {
productRepository.save(product);
return product;
}
I think you are missing something with the serialisation, do you know which lib you are using to serialize data to json ? (If not, can you copy/paste your dependency config file ? (pom.xml if you use maven)
If you use jackson for example, you need to add specific dependencies to your project, to have a proper serialization of dates. (You can refer to Java 8 LocalDate Jackson format for instance)

#JsonPropertyOrder names independent of property naming strategy

We have a model like:
#JsonPropertyOrder({
"id",
"alpha2_code",
"alpha3_code",
"name"
})
#Getter
#Setter
public class Country {
private Long id;
private String alpha2Code;
private String alpha3Code;
private String name;
}
and a ObjectMapper instance like:
var jsonMapper = new ObjectMapper();
jsonMapper.enable(SerializationFeature.INDENT_OUTPUT);
jsonMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
jsonMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
jsonMapper.registerModule(new JavaTimeModule());
to create the following JSON:
{
"id": 1,
"alpha2_code": "NL",
"alpha3_code": "NLD",
"name": "Netherlands",
}
This works all as expected.
What important to mention is that we are using #JsonPropertyOrder to sort the output.
This annotation requires the field names as how they are in the output; thus SNAKE CASE like "alpha2_code" and not "alpha2Code" as the Java property name.
Now we have a requirement to create YAML as out put as well (based on the same model).
But the naming convention for the YAML output needs to be KEBAB CASE.
Is there a smart way to solve this?
What I'm thinking of is to move the #JsonPropertyOrder to mix-ins and to introduce CountrySnakeMixin and CountryKebabMixin mix-in classes and use these in separate object mappers.
For this simple example it seems quite straightforward but with a model of 50 - 100 classes this becomes a maintenance nightmare.
I tested
This annotation requires the field names as how they are in the
output
as not correct:
#JsonPropertyOrder({
"id",
"alpha2Code",
"alpha3Code",
"name"
})
works as intended, also changing the order, both in snake and kebab case.

Why do I not get any results from MongoOperation when querying for an entity?

When I execute request to db
db.users.find({"name": "Max"})
I get this result
{"_id":ObjectId("5785718ee271a7c7ebaad28b"),"name":"Max","visits-by-day":[{"day":"Thursday","visitsAmount":20},{"day":"Saturday","visitsAmount":4}]}
JSON structure example:
{
"users": [
{
"name": "Bobby",
"visits-by-day": [
{
"day": "Sunday",
"visitsAmount": 8
},
{
"day": "Monday",
"visitsAmount": 3
}
]
}
]
}
Here my Java code
MongoUser user = mongoTemplate.findOne(query(where("name").is("Max")), MongoUser.class);
The model
#Document
public class MongoUser {
#Id
private String id;
private String name;
private List<VisitsPerDay> visitsByDay;
// Getters & Setters omitted
}
public class VisitsPerDay {
private String day;
private Integer visitsAmount;
// Getters & Setters omitted
}
Why Spring does return a null empty instead of serialized Java object?
By default, the collection queried for a given typed is derived from the simple name of the domain type you want to read. In your case, that would be mongoUser. To get your example to work, you basically have two options:
Explicitly configure the collectionName in the #Document annotation on MongoUser to users. That will basically tie instances of that class to that collection and let all data access operations for that class to work with that collection (e.g. for repositories etc.).
When calling MongoTemplate, use the overload of findOne(…) that takes an explicit collection name:
template.findOne(query(…), MongoUser.class, "users");

Fields duplicated during de-serialization

I have a class which looks like this :
public class Item {
private ItemHeader header;
private ItemBody body;
}
public class ItemHeader {
private int id;
private String name;
}
public class ItemBody {
private List<String> values;
private List<String> details;
}
The fields of ItemHeader and ItemBody are accessible via setters and getters from the Item class as well as from their corresponding classes. All said setters and getters EXCEPT get/setItemBody +get/setItemHeader are annotated with #JsonIgnore.
When Item instance is returned by a GET REST method, the Response looks as following :
{
"body": {
"details":[]
"values":[]
},
"header": {
"id": 145,
"name": "name_of_item",
},
"details":[],
"values":[],
"id": 145,
"name": "name_of_item"
}
Internals of itemHeader and itemBody are spilled twice into the deserialized Json , once (correctly) inside the corresponding fields and the second time just outside them.
I do not have much control over Jackson definitions behind the scenes and can basically only control my class hierarchy with annotations and such.
Please advise - how to remove the duplication, the "spilled over" values...
I've ended up removing the double layer of getters/setters which solved the issue, and in the process discovered that indeed, the serialization path and the de- path used different libraries . So https://stackoverflow.com/users/2513573/adamskywalker had the right idea

Default Property Naming Strategy in dropwizard

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);

Categories

Resources