Spring rest Json issue - spring

I found below answered question
Different names of JSON property during serialization and deserialization
Unfortunately this does not work when we use Spring Restful webservice. I am not sure what is cauisng the issue but it gives some Field abiguity exception.
What I want to do is Serialize and deserialize a field name with different names.
For e.g.
class Test {
private String name;
#JsonProperty("myName")
public String getName() {
return name;
}
#JsonProperty("yourName")
public void setName(String name) {
this.name = name;
}
}
This does not work in Spring rest

You can not set #JsonProperty for both (getter & setter). You can set for the field or setter method.
But you want different name for request and response, Create two classes like this.
class StudentResponse{
#JsonProperty(name="student_name)
private String name;
//getter & setter
}
class StudentRequest{
#JsonProperty(name="name)
private String name;
//getter & setter
}

Damith is right, you seem to not be able to mark both methods within the same class, however there is a way to solve this:
First off, you will have to Create a custom deserializer (or serializer, depends on your preference).
My example object:
#JsonDeserialize(using = ObjectDeserializer.class)
public class MyObject {
private String name;
public void setName(String name) {
this.name = name;
}
#JsonProperty("SomeOtherName")
public String getName() {
return name;
}
}
Note, i mark the getter as the property with the first name. And I give the class a custom deserializer. Which looks like that:
public class ObjectDeserializer extends JsonDeserializer<MyObject> {
#Override
public MyObject deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
MyObject object = new MyObject();
JsonNode node = jp.getCodec().readTree(jp);
JsonNode jsonNode = node.get("MyCustomSerializeName");
object.setName(jsonNode.getTextValue());
return object;
}
}
This class will create my custom object and get the name of the setter field description (rather than relying on the property name).
Put together, i get:
public class DeserializeTest {
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
MyObject o = new MyObject();
o.setName("Hello");
String writeValueAsString = mapper.writeValueAsString(o);
System.out.println(writeValueAsString);
String jsonObj = "{\"MyCustomSerializeName\":\"Other Test\"}";
MyObject readValue = mapper.readValue(jsonObj, MyObject.class);
System.out.println(readValue.getName());
}
}
And this outputs:
{"SomeOtherName":"Hello"}
Other Test
I hope that helps you.

Related

Jackson: Multiple Serializers on the same entity when differents Rest EndPoint are called

I'm trying to avoid using the DTO antipattern when different EndPoint are called, where each returns a distinct representation of the same entity. I'd like to take advantage of the serialization that Jackson performs when I return the entity in the Rest EndPoint. This means that serialization is only done once and not twice as it would be with a DTO (entity to DTO and DTO to Json):
EndPoints example:
#GetMapping("/events")
public ResponseEntity<List<Event>> getAllEvents(){
try {
List<Event> events = (List<Event>) eventsRepository.findAll();
return new ResponseEntity<List<Event>>(
events, HttpStatus.OK);
}catch(IllegalArgumentException e) {
return new ResponseEntity<List<Event>>(HttpStatus.BAD_REQUEST);
}
}
#GetMapping("/events/{code}")
public ResponseEntity<Event> retrieveEvent(#PathVariable String code){
Optional<Event> event = eventsRepository.findByCode(code);
return event.isPresent() ?
new ResponseEntity<Event>(event.get(), HttpStatus.OK) :
new ResponseEntity<Event>(HttpStatus.BAD_REQUEST);
}
Serializer (class that extends of StdSerializer):
#Override
public void serialize(Event value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
if(firstRepresentation) {
//First Representation
gen.writeStartObject();
gen.writeNumberField("id", value.getId());
gen.writeObjectField("creation", value.getCreation());
gen.writeObjectFieldStart("event_tracks");
for (EventTrack eventTrack : value.getEventsTracks()) {
gen.writeNumberField("id", eventTrack.getId());
gen.writeObjectField("startTime", eventTrack.getStartTime());
gen.writeObjectField("endTime", eventTrack.getEndTime());
gen.writeNumberField("priority", eventTrack.getPriority());
gen.writeObjectFieldStart("user");
gen.writeNumberField("id", eventTrack.getUser().getId());
gen.writeEndObject();
gen.writeObjectFieldStart("state");
gen.writeNumberField("id", eventTrack.getState().getId());
gen.writeStringField("name", eventTrack.getState().getName());
gen.writeEndObject();
}
gen.writeEndObject();
gen.writeEndObject();
}else if(secondRepresentation) {
//Second Representation
}
}
Entity:
#JsonSerialize(using = EventSerializer.class)
#RequiredArgsConstructor
#Getter
#Setter
public class Event implements Comparable<Event>{
private Long id;
#JsonIgnore
private String code;
private Timestamp creation;
#NonNull
private String description;
#JsonUnwrapped
#NonNull
private EventSource eventSource;
#NonNull
private String title;
#NonNull
private Category category;
#NonNull
#JsonProperty("event_tracks")
private List<EventTrack> eventsTracks;
#JsonProperty("protocol_tracks")
private List<ProtocolTrack> protocolTracks;
public void addEventTrack(#NonNull EventTrack eventTracks) {
eventsTracks.add(eventTracks);
}
#JsonIgnore
public EventTrack getLastEventTrack() {
return eventsTracks.get(eventsTracks.size() - 1);
}
#JsonIgnore
public int getLastPriority() {
return getLastEventTrack().getPriority();
}
public void generateUUIDCode() {
this.code = UUID.randomUUID().toString();
}
#Override
public int compareTo(Event o) {
return this.getLastPriority() - o.getLastPriority();
}
}
So, so far I have been able to serialize a representation type with a class that extend of StdDeserializer, but this doesn't give me the flexibility to extend the representations of the same entity attributes in multiple ways. Although I've tried it with Json annotations, but I realize that the more representations the entity class has, it can get very complex, something that it should be simple. Maybe some idea how I could do it.
Thank you.
If you want to define multiple representations of the same bean you could use Jackson JsonView.
With json views you can set different strategies to define which property will be serialized in the response and so use different views by endpoint.
Documentation here : https://www.baeldung.com/jackson-json-view-annotation
Just don't forget that you doing REST here....avoid expose too many representations of the same resource

JSON-B serializes Map keys using toString and not with registered Adapter

I have a JAX-RS service that returns a Map<Artifact, String> and I have registered a
public class ArtifactAdapter implements JsonbAdapter<Artifact, String>
which a see hit when deserializing the in-parameter but not when serializing the return value, instead the Artifact toString() is used. If I change the return type to a Artifact, the adapter is called. I was under the impression that the Map would be serialized with built-in ways and then the adapter would be called for the Artifact.
What would be the workaround? Register an Adapter for the whole Map?
I dumped the thread stack in my toString and it confirms my suspicions
at java.lang.Thread.dumpStack(Thread.java:1336)
Artifact.toString(Artifact.java:154)
at java.lang.String.valueOf(String.java:2994)
at org.eclipse.yasson.internal.serializer.MapSerializer.serializeInternal(MapSerializer.java:41)
at org.eclipse.yasson.internal.serializer.MapSerializer.serializeInternal(MapSerializer.java:30)
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:63)
at org.eclipse.yasson.internal.Marshaller.serializeRoot(Marshaller.java:118)
at org.eclipse.yasson.internal.Marshaller.marshall(Marshaller.java:74)
at org.eclipse.yasson.internal.JsonBinding.toJson(JsonBinding.java:98)
is the serializer hell-bent on using toString at this point?
I tried
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class PersonAdapter implements JsonbAdapter{
#Override
public String adaptToJson(Person obj) throws Exception {
return obj.getName();
}
#Override
public Person adaptFromJson(String obj) throws Exception {
return new Person(obj);
}
}
public class Test {
public static void main(String[] args) {
Map<Person, Integer> data = new HashMap<>();
data.put(new Person("John"), 23);
JsonbConfig config = new JsonbConfig().withAdapters(new PersonAdapter());
Jsonb jsonb = JsonbBuilder.create(config);
System.out.println(jsonb.toJson(data, new HashMap<Person, Integer>() {
}.getClass().getGenericSuperclass()));
}
}
but still ended up with the toString() of Person
Thanks in advance,
Nik
https://github.com/eclipse-ee4j/yasson/issues/110 (in my case since that's the default provider for WildFly)

#value not able to read from application.properties in springboot

I am trying to read value from properties file using #value as follows.
#Value("${abc}")
private String abc;
public List<Record> fetchRecords(String label, String predicate) {
System.out.println(abc);
}
but value of abc is coming as null. Whereas when I try to print the same using #PostConstruct, I am getting the expected value.
#PostConstruct
public void postconstruct() {
System.out.println(abc);
}
Any lead why I am not able to get the value in fetchRecords() method?
For reference, here goes the code
#Component
public class AuditRecord {
private String subject;
private String predicate;
private String oldObject;
private String newObject;
private String readOnlyAuthInfo;
#Value("${registry.system.base}")
private String registrySystemContext;
public void record(DatabaseProvider provider) throws AuditFailedException {
System.out.println("---registrySystemContext value showing null here---"+registrySystemContext);
...
}
#PostConstruct
public void postconstruct() {
System.out.println("---registrySystemContext value showing here as expected---"+registrySystemContext);
}
}
The way I am calling is as follows:
#Component
public class RegistryDaoImpl implements RegistryDao {
...
private void addOrUpdateVertexAndEdge(Vertex v, Vertex dbVertex, GraphTraversalSource dbGraph, String methodOrigin){
...
AuditRecord record = new AuditRecord();
record
.subject(dbVertex.label())
.predicate(e.label())
.oldObject(null)
.newObject(existingV.label())
.record(databaseProvider);
}
}
P.S. registry.system.base is in application.yml.
You need to autowire AuditRecord and not use new directly. Only that way you will have your class in Spring's context.
We don't know your exact usage of the class but you might be interested in Spring's FactoryBean.

How to force Jackson deserialize field values to lower case

I have spring application which expose REST endpoint, lets name it "doAction". As the request it consumes object:
class Person{
private String name;
private String email;
}
Some clients can call this endpoint by passing data with different practice of writing words, like:
Peter_1
name = Peter
email = peter#gmail.com (lower case)
Mark_2
name = mark
email = MARK#gmail.com (upper case)
Julia_3
name = julia
email = JuliaToward#gmail.com (camel case)
Is there some approach to force all income data be parsed to lowercase(lets assume all fields are Strings)?
So as a result I desire to have:
Peter_1
name = peter
email = peter#gmail.com
Mark_2
name = mark
email = mark#gmail.com
Julia_3
name = julia
email = juliatoward#gmail.com
Solution for Jackson is appreciated.
Short answer Call toLower in the setter
Here is an example:
class Animal
{
private String name;
public void setName(final String newValue)
{
StringUtils.trimToNull(StringUtils.lowerCase(newValue));
}
}
I also recommend either trimToNUll or trimToEmpty.
If you are using Spring Data Rest with spring mvc and you want all incoming string data to be in lower case then define following
public class StringSerializer extends StdDeserializer<String>{
public StringSerializer() {
this(null);
}
public StringSerializer(Class<String> vc) {
super(vc);
}
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonToken t = p.getCurrentToken();
if (t==JsonToken.VALUE_STRING){
String receivedValue = p.getText();
if (receivedValue == null)
return null;
else
return receivedValue.toLowerCase();
}else{
return null;
}
}
}
And following:
#Configuration
public class RestDataConfig extends RepositoryRestMvcConfiguration {
#Override
#Bean
public ObjectMapper halObjectMapper() {
ObjectMapper mapper = super.halObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StringSerializer());
mapper.registerModule(module);
return mapper;
}
}

Spring Boot String Deserializer for every form field

In our web app create and update forms have a size validation. For instance:
#Size(min = 4, max = 20)
private String mobile;
As seen the field is not required. But at the front-end user wants to clear field. Then form validation fails because of length restriction. Incoming data is an empty string instead of null. So minimum length validation restricts the input.
Therefore I start to search a solution to convert empty strings to null values. I found a #InitBinder and StringTrimmerEditor solution but our system uses #ResponseBody approach. So It doesn't fit.
Adding #JsonDeserialize(using = CustomTrimDeserializer.class) annotation or writing a custom setter for every string field is not DRY solution.
I just want to add app wide custom deserializer for String fields.
I finally examine the JsonComponentModule class and noticed spring is looking for the JsonComponent annotation for deserializer registration.
This is a one file spring boot project for solution
#RestController
#SpringBootApplication
public class CheckNullApplication {
public static void main(String[] args) {
SpringApplication.run(CheckNullApplication.class, args);
}
#PostMapping("/check-null")
public boolean checkNull(#RequestBody final HelloForm form) {
return form.getName() == null;
}
public static class HelloForm {
private String name;
public String getName() { return name; }
public void setName(final String name) { this.name = name;}
}
#JsonComponent
public static class StringTrimmerDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(final JsonParser p, final DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String result = StringDeserializer.instance.deserialize(p, ctxt);
if (result != null) {
result = result.trim();
if (StringUtils.isEmpty(result)) {
return null;
}
}
return result;
}
}
}
Instead of adding #JsonDeserialize annotation you may want to just register your custom deserializer via Module (for example, SimpleModule), and it will apply to all String valued properties. Something like:
SimpleModule module = new SimpleModule(...);
module.addDeserializer(String.class, new CustomTrimDeserializer());
mapper.registerModule(module);
Create a class as following and annotate with #JsonComponent. Spring boot will pick that up as a component.
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
#JsonComponent
public class WhitSpaceTrimmerDeserializer extends StringDeserializer {
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
final String value = super.deserialize(p, ctxt);
return value!=null?value.trim():null;
}

Resources