JPA projection with custom resulting field names - java

I use interface-based projections using named-native-query. My UserDTO looks like this:
public interface UserDTO {
#Value("#{target.USER_ID}")
String getId();
#Value("#{target.USER_NAME}")
String getName();
#Value("#{target.REGISTRATION_REGION}")
String getRegistrationRegion();
}
After that I marshall the list of DTOs to Json, and field names which I see there are in camel-case:
{"USERS": [
{
"id": "e00000099232200",
"name": 1616674065,
"registrationRegion": 1617344002
}]}
But I need them in DB style - Upper-case and with underscores like:
{"USERS": [
{
"ID": "e00000099232200",
"NAME": 1616674065,
"REGISTRATION_REGION": 1617344002
}]}
The straightforward way is naming my DTOs methods like getNAME or getREGISTRATION_REGION or iterating over Json fields and make them upper-case there. But is there a more elegant way to set the display name? Something like this:
#Display("REGISTRATION_REGION")
String getRegistrationRegion();

If you're using Jackson you can annotate your interface with:
#JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
Or if you want this behaviour globally across all usages of your mapper, configure it as follows:
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.UpperCamelCaseStrategy());
EDIT:
You can implement your own CaseStrategy, for your case it will be:
class UpperSnakeCase extends PropertyNamingStrategy.SnakeCaseStrategy{
#Override
public String translate(String input) {
String snakeCased = super.translate(input);
if (snakeCased == null) {
return null;
}
return snakeCased.toUpperCase();
}
}

Related

spring request body assign name to array / list

I am currently accepting the student list in Request Body.
public void saveStudents(#RequestBody List<Student> Students){
}
which is accepting below JSON
[
{
"name": "",
"rollNo": ""
},
{
"name": "",
"rollNo": ""
}
]
Instead of the above, I want to accept
{
"students": [
{
"name": "",
"rollNo": ""
},
{
"name": "",
"rollNo": ""
}
]
}
I have tried
public void saveStudents(#RequestBody #JsonProperty("students") List<Student> Students){
}
But it is not accepting.
I do not want to create another object which contains the student list.
Is there any way to assign name to array/list?
#RequestBody annotation stands for sent content - means JSON. There exists no additional mapping methods for scrambling those content inside the annotion, so body represents always an object which can be mapped.
jackson annotation #JsonProperty is defined to annotate fields/methods inside your JSON class - so has no effect at your controller method/parameters
Suggested way
You should IMHO simply accept that you need to create a new class, because it is the easiest and also the correct way to do: For example
import java.util.ArrayList;
import java.util.List;
public class StudentList {
// simplest example approach by public field...
public List<Student> students = new ArrayList<>();
}
And your controller code should look like:
#RequestMapping(path="/api/students",method = RequestMethod.POST)
public void saveStudents(#RequestBody StudentList list) {
// do stuff...
}
So the JSON in body represents a clazz. It's easy to maintain, easy to read, extendable, KISS and also uses spring/jackson defaults without any special configuration.
Other, not suggested ways
I thought about other options to handle this, but none is KISS or good maintainable:
Converter
You could write a converter class, see https://www.baeldung.com/spring-httpmessageconverter-rest
But I think this is an overkill / over engineering to solve avoiding a new value class.
Parse JSON (extreme ugly)
You could handle JSON parsing directly
#RequestMapping(path="/api/students",method = RequestMethod.POST)
public void saveStudents(#RequestBody String json) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = Json.node(json);
JsonNode students = node.get("students");
// ... than convert back to a list containing students ...
}
But this would be ... terrible to maintain, not KISS , not readable .
You can use:
saveStudents(#RequestParam(value="students") List<Student> students)
saveStudents(#RequestBody Map<String,List<Student>> body)
Hope that could help you.

How deserialize json object array as array of json strings?

Consider json input:
{
companies: [
{
"id": 1,
"name": "name1"
},
{
"id": 1,
"name": "name1"
}
],
nextPage: 2
}
How deserialize this into class:
public class MyClass {
List<String> companies;
Integer nextPage;
}
Where List<String> companies; consists of strings:
{"id": 1,"name": "name1"}
{"id": 1,"name": "name1"}
#JsonRawValue doesn't work for List<String> companies;
Is there a way to configure Jackson serialization to keep companies array with raw json string with annotations only? (E.g. without writing custom deserializator)
There is no annotation-only solution for your problem. Somehow you have to convert JSON Object to java.lang.String and you need to specify that conversion.
You can:
Write custom deserializer which is probably most obvious solution but forbidden in question.
Register custom com.fasterxml.jackson.databind.deser.DeserializationProblemHandler and handle com.fasterxml.jackson.databind.exc.MismatchedInputException situation in more sophisticated way.
Implement com.fasterxml.jackson.databind.util.Converter interface and convert JsonNode to String. It is semi-annotational way to solve a problem but we do not implement the worst part - deserialisation.
Let's go to point 2. right away.
2. DeserializationProblemHandler
Solution is pretty simple:
ObjectMapper mapper = new ObjectMapper();
mapper.addHandler(new DeserializationProblemHandler() {
#Override
public Object handleUnexpectedToken(DeserializationContext ctxt, JavaType targetType, JsonToken t, JsonParser p, String failureMsg) throws IOException {
if (targetType.getRawClass() == String.class) {
// read as tree and convert to String
return p.readValueAsTree().toString();
}
return super.handleUnexpectedToken(ctxt, targetType, t, p, failureMsg);
}
});
Read a whole piece of JSON as TreeNode and convert it to String using toString method. Helpfully, toString generates valid JSON. Downside, this solution has a global scope for given ObjectMapper instance.
3. Custom Converter
This solution requires to implement com.fasterxml.jackson.databind.util.Converter interface which converts com.fasterxml.jackson.databind.JsonNode to String:
class JsonNode2StringConverter implements Converter<JsonNode, String> {
#Override
public String convert(JsonNode value) {
return value.toString();
}
#Override
public JavaType getInputType(TypeFactory typeFactory) {
return typeFactory.constructType(new TypeReference<JsonNode>() {
});
}
#Override
public JavaType getOutputType(TypeFactory typeFactory) {
return typeFactory.constructType(new TypeReference<String>() {
});
}
}
and now, you can use annotation like below:
#JsonDeserialize(contentConverter = JsonNode2StringConverter.class)
private List<String> companies;
Solutions 2. and 3. solve this problem almost in the same way - read node and convert it back to JSON, but uses different approaches.
If, you want to avoid deserialising and serialising process you can take a look on solution provided in this article: Deserializing JSON property as String with Jackson and take a look at:
How to serialize JSON with array field to object with String field?
How to get a part of JSON as a plain text using Jackson
How to extract part of the original text from JSON with Jackson?

Spring Boot - Different model representations / Multiple APIs

my backend must offer two different APIs - different access to the same models, respectively, same implementation and same mappings to the database. Models are send as JSONs and they are consumed by the backend in the same way.
But different JSON representations are necessary on each API.
F.e. I'd like to name some fields differently (w/ #JsonProperty f.e.) or want to omit some.
As mentioned, they should be consumed by the controllers in the same way they are produced.
Since only the representation differs: is there a simple and DRY compliant way to accomplish this?
Example to this:
Calling
ProductsController.java
sym/products/1
should return
{
"id": 1,
"title": "stuff",
"label": "junk"
}
and calling
ProductsController.java
frontend/products/1
should return
{
"id": 1,
"label": "junk",
"description": "oxmox",
"even-more": "text"
}
Thanks a lot!
Tim
Separate DTOs may be the best solution.
An alternate (assuming you are using Jackson) is to have one DTO with the all the different fields, and then use MixIns to control how the DTO is serialized.
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(SomeDTOWithLabel.class, IgnoreLabelMixin.class);
SomeDTOWithLabel dto = new SomeDTOWithLabel();
dto.setLabel("Hello World");
dto.setOtherProperty("Other property");
String json = objectMapper.writeValueAsString(dto);
System.out.println("json = " + json);
}
public static class SomeDTOWithLabel {
private String label;
private String otherProperty;
public String getOtherProperty() {
return otherProperty;
}
public void setOtherProperty(String otherProperty) {
this.otherProperty = otherProperty;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
public abstract class IgnoreLabelMixin {
#JsonIgnore
public abstract String getLabel();
}
}
For instance we have DTOs with deprecated properties that old clients may still depend on, but we don't want to send them to newer client, so we use MixIns to supress them.
If this is simply a case of returning a lightweight payload depending on which path you call, you can configure your json serializer (ObjectMapper) to omit empty fields. Then in your service only select and populate the subset of fields you wish to return.
objectMapper.setSerializationInclusion(Include.NON_NULL); // omits null fields
However, if you wish to return differently named fields, use a different API model.

Parsing JSON with a value with dynamic type (either string or object) with Jackson's ObjectMapper

I am new to Jackson and I am having some problems determining the best way to deal with processing JSON files that are dynamic in nature. I know I could solve the issue with the streaming or tree API, but this would involve a lot of code which will not be easily maintained. For example, take the following two json files:
{
something: "somethingValue"
somethingelse: "anotherValue"
url: "http://something.com"
}
and
{
something: "somethingValue"
somethingelse: "anotherValue"
url: {
service1: [
"http://something.com",
"https://something.com" ],
service2: [
"http://something2.com",
"https://something2.com" ],
}
}
the default behaviour of the first json object after being parsed, should add the URL to both service1 and service2 url lists in the subclass "URL". where the second allow specifying very specific urls to each. The data object for url class I was planning on use is as follows:
public class url {
// ideally, I would use the java.net.URL instead of String
public List<String> service1;
public List<String> service2;
// also includes getter/setters using a fluent style
...
}
There would also be some other parent class which would have a parameter for URL and other first level json parameters.
What is the best way to handle this in jackson?
The second one is not valid JSON, this is :
{
"something": "somethingValue",
"somethingelse": "anotherValue",
"url": {
"service1" : [
"http://something.com",
"https://something.com" ],
"service2" : [
"http://something2.com",
"https://something2.com" ]
}
}
You can create it/consume it with class A which looks like following
class A{
String something;
String somethingElse;
B url;
}
class B{
Str service1;
List<String> service2;
}
To achieve anything dynamically no matter what, you have to put it in Lists, therefore instead of solution above, you can do this
class A{
String something;
String somethingElse;
B url;
}
class B{
List<C> services;
}
class C{
List<String> service;
}
I ended up mixing JsonNode to get this working.
public class Foo {
#JsonProperty("something")
private String something;
#JsonProperty("somethingelse")
private String somethingelse;
#JsonProperty("url")
JsonNode url;
// getters setters
public static Foo parse(String jsonString) {
ObjectMapper mapper = new ObjectMapper();
Foo foo = mapper.readValue(jsonString, Foo.class);
return foo;
}
public static boolean validate(Foo foo) {
JsonNode url = foo.path("url");
if (url.isTextual()) {
// this is the first case {"url": "http://something.com"}
System.out.println(url.getTextValue());
} else {
// This is the second case
}
}
}
Answer:
After struggling with Jackson to do what I want in a simple and elegant way, I ended up switching to Gson library for JSON parsing. it allowed me to create a custom deserializer for my class that was extremely easy.
An example of something similar that I did can be found here:
http://www.baeldung.com/gson-deserialization-guide
I appreciate the help and guidance with Jackson, however it just made me realize that Jackson was just not going to meet my needs.
-Stewart

Embedding a Jackson JsonNode in POJO stored in CrudRepository

Here's where I'm at. I've an MVC controller method that accepts JSON content. Because I need to validate it using JSON Schema, my controller maps the request body as a Jackson JsonNode.
Upon successful validation, I need to persist the data in Spring Couchbase repository. Consider the following snippet:
public class Foo
{
#Id
private String _id;
#Version
private Long _rev;
#Field
private JsonNode nodeData;
// .. Other data and members.
}
//
// Repository
//
#Repository
public interface FooRepository extends CrudRepository<Foo, String> {
}
When I store these elements into the Couch repository, what I'd like to see is something like this:
{
"_class": "Foo",
"field1": "field 1 data",
"nodeData" : {
"Some" : "additional data",
"from" : "JsonNode"
}
}
instead, what I see in the repository is something like this:
{
"_class": "Foo",
"field1": "field 1 data",
"nodeData" : {
"_children": {
"Some": {
"_value": "additional data",
"_class": "com.fasterxml.jackson.databind.node.TextNode"
},
"From": {
"_value": "jsonNode",
"_class": "com.fasterxml.jackson.databind.node.TextNode"
},
"_nodeFactory": {
"_cfgBigDecimalExact": false
}
}
}
Each stored property of the JsonNode is decorated with class information, and other meta-data, which is not desirable.
My question - is there a preferred way to get the CrudRepository to behave in the manner that I wish?
It doesn't work that way because serialization and de-serialization conventions are already established. You can override these conventions with custom serialization & de-serialization in Jackson-- but that might go beyond the "crude" approach you are looking for.
I see you want a one shoe fits all approach to data modeling.
Might i recommend storing a Map
#Field
private Map<String, String> data;
This map is private so its perfect.
You can then have two methods
one method puts to the map like so
ObjectMapper mapper = new ObjectMapper()
public void setFeild(String name, Object value) {
ObjectNode node new ObjectNode(JsonNodeFactory.instance);
node.put("clazz", value.getClass().getName());
if (value instance of String) {
node.put("value", value)
} else {
node.put("value", mapper.writeValueAsString(data));
}
data.put(name, node.toString());
}
the other gets from the map like so
public Object getField(String name) {
if (data.contains(name)) {
JsonNode node = mapper.readValue(data.get(name), JsonNode.class);
Class clazz = Class.forName(node.get("class").textValue());
if (clazz.equals(String.class) {
return node.get("value").textValue();
} else {
return (Object) mapper.readValue(node.get("value"), clazz);
}
}
}
You should update this implementation to handle Date, Integer, Boolean, Double ... etc the same way i am handling String-- POJOs are what you serialize/de-serialize to/from json.
I hope this makes sense.

Categories

Resources