I have a custom serializer class
public class LocalDateTimeSerializer extends StdSerializer<LocalDateTime>{
#Override
public void serialize(LocalDateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
// custom code to serialize
jgen.writeString(newValue);
}
I use it as below on a class variable:
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime localDateTime;
Now, when I serialize this is json, the response datetime is serialized as expected according to the custom logic. All good here.
But there are some cases, when a field is mapped twice, for no apparent reason. The class variable is declared exactly the same to serialize with the custom serializer, but the output would have two fields for the variable. One would be the expected field serialized. The second would be the non-serialized field. Look at the following output:
"localDateTime": //as expected,
"localdateTime":{"year":2017,"month":"JUNE","monthValue":6,"dayOfMonth":29,"hour":20,"minute":0,"second":0,"nano":0,"dayOfWeek":"SATURDAY","dayOfYear":180,"chronology":{"calendarType":"iso8601","id":"ISO"}}
This is randomly happening for say two fields of a class.
Any idea what could cause this?
UPDATE:
I think it was something to do with how the getter/setter for the entity was named as.
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime aOldDate;
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime aNewDate;
public LocalDateTime getaOldDate() {
return aOldDate;
}
public void setaOldDate(LocalDateTime aOldDate) {
this.aOldDate= aOldDate;
}
public LocalDateTime getANewDate() {
return aNewDate;
}
public void setANewDate(LocalDateTime aNewDate) {
this.aNewDate= aNewDate;
}
If I try serializing this, the aNewDate field would be present twice in the json, and the aOldDate would be present once. It is probably because the 'A' is capital in the getter/setter for aNewDate, and this causes some problems. Not entirely sure what happens but relates to section 8.8 in this document: https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf
Related
Rest Controller:
#RequestMapping(value = "/admin/rest/new-subscriptions")
public List<NewSubscriptionDTO> getNewSubscriptions() {
NewSubscriptionDTO dto = new NewSubscriptionDTO();
dto.setId("54");
dto.setName("John Doe");
return Arrays.asList(dto);
}
NewSubscriptionDTO:
package dermatica.web.admin.rx;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.joda.time.DateTime;
import java.io.Serializable;
public class NewSubscriptionDTO implements Serializable {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I get the following exception:
no properties discovered to create BeanSerializer (to avoid exception,
disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
If I annotate the fields with #JsonProperty it work fine.
Is there a way for the serialization to work automatically without needing this annotation?
#JsonProperty auto-generates a getter/setter that Jackson uses to read/write to the fields during serialization/deserialization. Here are some alternative approaches:
Provide your own public getters/setters for all fields
Make the fields public, generally frowned upon, but if you're creating a simple DTO, that may be acceptable.
Setting ObjectMapper Visibility for FIELD to ANY (see here)
Disable the FAIL_ON_EMPTY_BEANS exception (see here)
Given that your DTO class has getters and setters, this should work without #JsonProperty. I wasn't able to reproduce the exact error message you showed, but here are some suggestions that may help:
[Controller] Explicitly specify the method type as GET, either using method = GET or #GetMapping - not necessary, but it's good to be explicit
[Controller] Make sure you annotate the controller class with #RestController, indicating the response is serialized to JSON and wrapped in an HttpResponse object.
[DTO] You don't need to extend Serializable (see here).
The final controller would look like this:
#RestController
public class MyController {
#GetMapping(value = "/admin/rest/new-subscriptions")
public List<MyDTO> getDTO() {
MyDTO dto = new MyDTO();
dto.setId("54");
dto.setName("John Doe");
return Collections.singletonList(dto);
}
}
Response:
[{"id":"54","name":"John Doe"}]
I have the following class
public class WorkpoolId implements Serializable {
#NotNull
private Long id = null;
#JsonCreator
public WorkpoolId(#JsonProperty("workpoolId") long id) {
this.id = Long.valueOf(id);
}
public WorkpoolId(Long id) {
this.id = id;
}
public WorkpoolId(String id) {
this.id = Long.valueOf(id);
}
private WorkpoolId() {
}
}
when mapping
"workpoolId":1
to this class I get a
javax.ws.rs.ProcessingException: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of WorkpoolId (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1)
Why is jackson not able to use the long constructor for the number value?
It fails because your WorkpoolId does not have access to field workpoolId it is not in its context anuymore. When your JSON is deserialized it could be deserialized either as an
independent object (having no field workpoolId, it IS the workbookId)
field object value in an object containing -say Data - it that might be named as workpoolId.
Now the use of workbookId could be usable for the JsonCreator in Data when constructing its field workpoolId.
To clarify this a bit, here is an example of possible Data class:
#Getter #Setter
public class Data {
private WorkpoolId workpoolId;
#JsonCreator // here it is a property!
public Data(#JsonProperty("workpoolId") long id) {
this.workpoolId = new WorkpoolId(id);
}
}
Json would be like {"workpoolId":1}
To have it work just remove the annotation #JsonProperty("workpoolId") from the attribute declaration. Actually the whole #JsonCreator annotation is not needed.
I am trying to use Formatter in webflux application but its throws
java.lang.IllegalStateException: Iterating over a toIterable() /
toStream() is blocking, which is not supported in thread
reactor-http-nio-2
Exception indicate i can't use block and method is expecting an object of PetType. I wanted to know if there is any other way to do this
#Component
public class PetTypeFormatter implements Formatter<PetType> {
private final PetTypeService petTypeServive;
public PetTypeFormatter(PetTypeService petTypeServive) {
this.petTypeServive = petTypeServive;
}
#Override
public String print(PetType petType, Locale locale) {
return petType.getName();
}
#Override
public PetType parse(String text, Locale locale) throws ParseException
{
Iterable<PetType> findPetTypes = petTypeServive.findAll().toIterable();
for (PetType type : findPetTypes)
{
if (type.getName().equals(text)) {
return type;
}
}
throw new ParseException("type not found: " + text, 0);
}
}
Edit:
The method signature of controller which i am using is
#PostMapping("/pets/new")
public String processCreationForm(#ModelAttribute("owner") Owner owner, #Valid Pet pet,BindingResult result, ModelMap model)
and the Pet class petType property which i was setting through the custom formatter when using webmvc
Edit2:
#Setter
#Getter
public class Pet
{
private String id;
private PetType petType;
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;
private String name;
}
#Setter
#Getter
public class PetType
{
private String name;
#Override
public String toString() {
return name;
}
}
You are trying to implement blocking business logic in a formatter.
The purpose of the Formatter<T> interface is to write custom parsing of strings, for example json strings, csv strings etc. and parse these into an object.
What you are doing is making a database call in a formatter, which is NOT the purpose of the formatter interface.
Since you have not shown us:
the purpose of the formatter
where the formatter is used
whats in the passed string into the formatter
what your request looks like
What a Pet class is
What a PetType is
I can't help you more than this. You are trying to do a blocking call in a webflux application in an interface that does not allow reactive coding (it returns a concrete value), You need to rethink your solution to the problem.
Please explain what your problem is and what it is you want to do, and not the problem with the code, the problem you are trying to solve, and we might be able to help you more.
Let's say I have the following entity:
public class Employee {
private String name;
private Company company
}
And I have a String with the content below:
{
"name":"Joe",
"company": "http://localhost/companies/23"
}
Spring Data Rest is capable of converting this JSON to an Employee object out of the box, but to how convert it manually?
OK. I think I understand the problem now. Of course SDR has to have an ObjectMapper which is capable to convert the incoming JSON into an entity (including hateoas links), but it seems that's NOT the default ObjectMapper and it's not even exported as a Bean.
So I made some reverse-engineering and I think I've found what you need. Fortunately the ObjectMapper which is used internally has a public getter in the RepositoryRestMvcConfiguration class, so it can be used easily:
/**
* The Jackson {#link ObjectMapper} used internally.
*
* #return
*/
public ObjectMapper objectMapper() {
return mapper.get();
}
I think the following code will work:
#Autowired
RepositoryRestMvcConfiguration rrmc;
private <T> T readValue(String json, Class<T> type)
throws IOException, JsonParseException, JsonMappingException {
return rrmc.objectMapper().readValue(json, type);
}
#Aurowired
private final RepositoryInvokerFactory repositoryInvokerFactory;
private Object loadPropertyValue(Class<?> type, String href) {
String id = href.substring(href.lastIndexOf('/') + 1);
RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(type);
return invoker.invokeFindById(id).orElse(null);
}
I have the following interface
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "className")
public interface InfoChartInformation {
public String name();
}
And the following implementation (enum):
public class InfoChartSummary {
public static enum Immobilien implements InfoChartInformation {
CITY, CONSTRUCTION_DATE;
}
public static enum Cars implements InfoChartInformation {
POWER, MILEAGE;
}
}
Then I use all of It in the following entity:
#Entity(noClassnameStored = true)
#Converters(InfoChartInformationMorphiaConverter.class)
public class TestEntity{
#Id
public ObjectId id;
#Embedded
public List<InfoChartInformation> order;
}
Jackson, in order to detect the type on the unmarshalling time, will add to every enum on the list the className.
I thought morphia would do the same, but there's no field className in the List of enum and the unmarshalling cannot be done correctly: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.RuntimeException: java.lang.ClassCastException: java.lang.String cannot be cast to com.mongodb
.DBObject
I guess the correct behavior should be to save all the enum route (package+name), not only the enum name. At least in that way the unmarshalling could be performed. There's a way morphia supports that by default or I need to create my own converter (similar to this) ?
I tried creating a Custom Converter:
public class InfoChartInformationMorphiaConverter extends TypeConverter{
public InfoChartInformationMorphiaConverter() {
super(InfoChartInformation.class);
}
#Override
public Object decode(Class targetClass, Object fromDBObject, MappedField optionalExtraInfo) {
if (fromDBObject == null) {
return null;
}
String clazz = fromDBObject.toString().substring(0, fromDBObject.toString().lastIndexOf("."));
String value = fromDBObject.toString().substring(fromDBObject.toString().lastIndexOf(".") + 1);
try {
return Enum.valueOf((Class)Class.forName(clazz), value);
} catch (ClassNotFoundException e) {
return null;
}
}
#Override
public Object encode(final Object value, final MappedField optionalExtraInfo) {
return value.getClass().getName() + "." + ((InfoChartInformation) value).name();
}
}
Then, I added the converter information to morphia morphia.getMapper().getConverters().addConverter(new InfoChartInformationMorphiaConverter());.
However, when serializing (or marshalling) the object to save it into the database, the custom converter is ignored and the Enum is saved using the default Morphia converter (only the enum name).
If I use in the TestEntity class only an attribute InfoChartInformation; instead of the List<>InfoChartInformation>, my customer converter will work. However I need support for List
Use:
public class InfoChartInformationMorphiaConverter extends TypeConverter implements SimpleValueConverter
It is a marker interface required to make your Convertor work.