Jackson unwrapping of null value objects - java

Let's say I have Description value object:
#JsonInclude(value = JsonInclude.Include.ALWAYS)
public class Description {
#Column(name = "DESCRIPTION")
private final String description;
}
and Product entity:
#Entity
public class Product extends AbstractEntity<Long> {
#JsonUnwrapped
private final Description description;
}
I created custom serializer for Description:
static class DescriptionSerializer extends StdSerializer<Description> {
DescriptionSerializer() {
super(Description.class);
}
#Override
public void serialize(Description value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
if (value != null) {
jgen.writeString(value.getDescription());
} else {
jgen.writeNull();
}
}
}
When I create:
Product product = new Product(new Description("description"));
and serialize it:
String result = mapper.writeValueAsString(spec);
it returns JSON: {"description":"description"}
When I create:
Product product = new Product(null);
it returns {},
but I would expect {"description":null}
If I remove #JsonUnwrapped, it works as I'd expect, but for non-null Description, it would create nested object
Is there a way to keep unwrapping for fields with null value objects in a similar way how it is done for built-in Java types?

To have what you want you should try with:
Product p = new Product(new Description(null));
In the json
{"description":"description"}
the Description, as a container of other fields exists, what is null is the value of the description field inside the Description object.
The two kind of serializations have different meaning.
To easily manage it in your code you can have a class like:
public class Product {
private Description description;
public Product(Description d) {
this.description = d;
}
public Product() {
this(new Description()); //all fields are null
}
public Product(String description) {
return new Product(new Description(description));
}
}
The last two constructor I would prefer as a factory methods, but it is just matter of code style.

Related

Is it possible to map a JPA/Hibernate Entity field to a field in another class without #Embeddable?

I have a very simple Entity (Person.java) that I am wanting to persist via JPA/Hibernate.
The Entity contains two fields: ID and Identification String.
The ID is a simple Integer, and is no problem. The Identification String is currently a String, but for various reasons, I want to instead use a wrapper class for String (IDString), where there are various validation methods among other things.
I am wondering how I can get JPA/Hibernate to use the wrapped string (inside the custom class IDString) when persisting the Person table in the database. I know this can probably be solved by letting the IDString be #Embeddable and then embed IDString in the Person entity with #Embedded, but I am looking for another method, mostly because IDString is in an entirely different package, and I am reluctant to have to go there and change stuff.
Googling, I found https://www.baeldung.com/hibernate-custom-types, but it seems to be mostly about more complicated cases, where you want to convert one class into another type, and I do feel that there is probably a smarter way that I am simply overlooking.
Here is the entity (in theory)
#Entity(name="Person")
#Table(name="DB_TABLE_PERSON")
public class Person implements Serializable {
#Id
Integer id;
// WHAT SHOULD I PUT HERE? I WANT TO SIMPLY USE THE STRING INSIDE IDSTRING AS THE FIELD TO PERSIST
IDString idString;
// getter and setter for ID.
public void getIdString() {
return idString.getValue();
}
public void setIdString(String in) {
idString.setValue(in);
}
}
And here is the class IDString (in theory):
public class IDString {
// I really want to be a POJO
private final String the_string;
public IdString(String input) {
if (isValid(input)) {
the_string = input;
} else {
throw new SomeCoolException("Invalid format of the ID String");
}
public boolean isValid(String input) {
// bunch of code to validate the input string
}
public String getValue() {
return the_string;
}
public void setValue(String input) {
if (isValid(input)) the_string = s;
else throw new SomeCoolException("Invalid format of the ID String");
}
I know that I could place the validation if the IDString inside the Entity, but the IDString will be used elsewhere (it's a general custom class), so I don't want to do that. Is there a simple way?
#Converter(autoApply=true) // autoApply is reasonable, if not use #Converter on field
public class IDStringConverter implements AttributeConverter<IDString,String> {
#Override
public String convertToDatabaseColumn(IDString attribute) {
return attribute != null ? attribute.getValue() : null;
}
#Override
public IDString convertToEntityAttribute(String dbData) {
return dbData != null ? new IDString(dbData) : null;
}
}
With this you should not need any other modifications in your code. One limitation of the AttributeConverter is that it maps from exactly 1 Java field to exactly 1 DB column. If you wanted to map to more columns (not the case here), you would need embeddables.
You could also put a #Column annotation on the getter:
#Entity
public class Person {
private final IdString idString = new IdString();
#Column(name = "ID_STRiNG")
public IdString getIdString() {
return idString.getValue();
}
public void setIdString(String input) {
idString.setValue(input);
}
Another solution could be to convert to/from IdString using #PostLoad and #PrePersit event handlers:
#Entity
public class Person {
#Column(name = "ID_STRiNG")
private String the_string; // no getters & setters
#Transient
private final IdString idString = new IdString();
#PostLoad
public void postLoad() {
idString.setValue(the_string);
}
#PrePersist
public void prePersist() {
the_string = idString.getValue();
}
// getters & setters for idString

Is it possible to make Jackson ObjectMapper deserialization fail when no property is mapped resulting an empty object?

I'm struggling with a problem: is it possible to configure Jackson to throw an error if no field is mapped?
Example: deserializing an empty object("{}") or without any of the fields that the target object contains.
I always check if some fields is null after get Jackson deserialize api. But i think you can extends Jackson deserializer to rewrite deserialize method to achieve your purpose.
Check if it equals to empty object:
#NoArgsConstructor
#Getter
#Setter
#ToString
#EqualsAndHashCode
#JsonIgnoreProperties
public class Foo {
private String a;
private String b;
private String c;
private String d;
}
public class FooMain {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final Foo EMPTY_FOO = new Foo();
public static void main(String[] args) {
try {
// This attempt will throw an exception:
Optional.ofNullable(OBJECT_MAPPER.readValue("{}", Foo.class))
.filter(foo1 -> !foo1.equals(EMPTY_FOO))
.orElseThrow(IllegalArgumentException::new);
// This attempt will not throw an exception:
Optional.ofNullable(OBJECT_MAPPER.readValue("{a:\"123\"}", Foo.class))
.filter(foo1 -> !foo1.equals(EMPTY_FOO))
.orElseThrow(IllegalArgumentException::new);
} catch (JsonProcessingException e) {
// cannot deserialize json string to FOO
}
}
}
if all from as set of fields are required:
If using the constructor for deserializing the object you can use required from #JsonProperty annotation.
For example, for a class Foo, a field name is required:
class Foo
{
String name;
Integer number;
#JsonCreator
public Foo(
#JsonProperty(value = "name", required = true) String name,
#JsonProperty(value = "number") Integer number)
{
this.name = name;
this.number = number;
}
// ... more methods ...
}
When trying to deserialize from an JSON with no name property, it will fail with MismatchedInputException:
objectMapper.readValue("{}", Foo.class); // FAILURE
Note that if JSON object explicitly sets the field as null, it will succeed:
objectMapper.readValue("{\"name\": null}", Foo.class); // SUCCESS
if any from a set of fields must be present:
This is a simple variation of the previous case, as we can put validation logic inside the #JsonCreator-annotated constructor.
For example:
class Foo
{
String name;
Integer number;
#JsonCreator
public Foo(
#JsonProperty("name") String name,
#JsonProperty("number") Integer number)
{
if (name == null && number == null)
throw new IllegalArgumentException("at least one of (name, number) fields must be non-null");
this.name = name;
this.number = number;
}
// ... more methods ...
}

Object to string delimited format

I have set of objects of different types.
Ex : Employee emp, adress adr
These two classes have list of properties
public class Employee{
private Stringname;
private int age;
}
public class Adress {
private String HouseNo;
private string Street;
private string pin;
}
Each attribute is assigned with some 2 character value
Name (NA), age (AG), HouseNo(HN),Street(ST), pin(PN)
I need to construct a string with these data and delimit with a %
Output:
NA%Vidhya%AG%30%HN%80%ST%1st cross%PN%100100
Each class knows it own data best so I would let each class be responsible for generating the string. As I understand it the two char codes for each field are unique for each class and member and only used when generating the string so only the class would need them.
interface AttributeDescription {
String generateDescription();
}
public class Employee implements AttributeDescription {
//members...
public String generateDescription() {
return String.format(“NA%%%s%%AG%%%d”, name, age)
}
Then simply call this method for all objects implementing the interface.
AttributeDescription object = ...
String attr = object.generateDescription();
I don't think it can be generalized more than this given the requirements.
Update
It might be better to have a builder class for building the string to get a more unified behavior between classes. Here is an example
public class AttributeBuilder {
private builder = new StringBuilder();
public String getAttribute() {
return builder.toString();
}
public void add(String code, String value) {
if (value == null) {
return;
}
builder.append(code);
builder.append(‘%’);
builder.append(value);
builder.append(‘%’);
}
}
And then you would also have to implement add(...) methods for other data types in a similar fashion. The builder could then be used like
public String generateDescription() {
AttributeBuilder builder = new AttributeBuilder();
builder.add(“NA”, name);
builder.add(“AG”, age);
return builder.getAttribute();
}

Can I prefix a function with "get" in Springboot

I have a Mcq class associated to a MongoRepository, and I want to get an instance of my Mcq which apply several changes (Answers shuffle, Questions draw, etc). I declared my function "myMcq.getInstance()", but I can't do that because every time I want to send a Mcq in a ResponseEntity there is an error in the JSON output because Springboot thinks that there is a "instance" property in my class.
Here is my java class :
#Document(collection = "Mcqs")
public class Mcq {
#Id public String id;
#DBRef public User creator;
public String title;
public String categoryID;
public List<McqChapter> chapterList = new ArrayList<>();
public Difficulty difficulty;
public Mcq() {}
public Mcq(String title) {
this();
this.title = title;
}
public ArrayList<String> getQuestionsIDs() {
ArrayList<String> result = new ArrayList<>();
for (McqChapter chapter : chapterList) result.addAll(chapter.getQuestionIDs());
return result;
}
public McqInstance getInstance() {
return new McqInstance(this);
}
}
To prevent the error add #JsonIgnore to getInstance() method:
#JsonIgnore
public McqInstance getInstance() {
return new McqInstance(this);
}
Marker annotation that indicates that the annotated method or field is to be ignored by introspection-based serialization and deserialization functionality. That is, it should not be consider a "getter", "setter" or "creator".

How to write a generic getObject() method for json string?

Got into a very basic issue. I have to convert json string to objects. I have a custom method as below which is expected to convert into corresponding class and throw an exception if it is not able to get the object out of it.
protected <T> T getObjectFromJson(Class<T> c, String json){
try{
Gson gson = new Gson();
T object = gson.fromJson(json, c);
return object;
} catch (Exception e){
throw new TMMIDClassConversionException(e.getCause(), e.getMessage());
}
}
The issue is this method is not throwing exception if I am trying to convert json of a different class.
My class
public class CompanyCategoryMap {
private Integer id;
private int mid;
private String catKey;
private String catValue;
private int priority;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getMid() {
return mid;
}
public void setMid(int mid) {
this.mid = mid;
}
public String getCatKey() {
return catKey;
}
public void setCatKey(String catKey) {
this.catKey = catKey;
}
public String getCatValue() {
return catValue;
}
public void setCatValue(String catValue) {
this.catValue = catValue;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
}
When I pass json string of Company rather than String of above class, it does not throw exception.
The string:
"{\"id\":6,\"name\":\"abc\",\"usersCount\":10,\"mid\":3,\"createdAt\":\"Sep 15, 2014 7:02:19 PM\",\"updatedAt\":\"Sep 15, 2014 7:02:19 PM\",\"active\":true,\"currency\":\"abc\",\"source\":\"unknown\",\"user_id\":1,\"tierId\":1}"
I think I am doing this conversion in a wrong way. What is the suggested way of doing it?
Take for example:
class Foo {
private String value;
}
class Bar {
private String value;
}
and
String json = "{\"value\" : \"whatever\"}";
new Gson().fromJson(json, Foo.class);
new Gson().fromJson(json, Bar.class);
Why should Gson reject any of these?
Gson is setup to perform a best effort to deserialize the given JSON into an instance of the given Class. It will map as many fields as it finds. If none are found, that's too bad.
Other libraries like Jackson do the opposite. By default, Jackson rejects any JSON which doesn't contain a mapping for every given class property. You can also configure it to ignore some properties.
Keep doing what you are doing. As the application writer, you should know when to use a Class instance with the appropriate JSON source.

Categories