#JsonInclude filtering by class' attribute - java

I am working on serializing classes to XML. I've found #JsonInclude to be immensely helpful. However, I am having issues on filtering a class by its attribute. In the xml, if it's a US address I need to have one set of annotations for the fields using #JsonProperty. Because of this, I'm trying to use the Include annotation to only show the relevant field based on the country.
The US address needs to have USAddress as the wrapper element name. Foreign Addresses need to have ForeignAddress as the wrapper element name as well as different element names for state/zip.
Is there a way to access the class and it's attributes in the filter? I've tried using the super class for both types of addresses, but with Bazel, I run into circular dependency issues when doing that. Im super new to bazel :P
public class Thing {
#JsonInclude(value = Include.CUSTOM, valueFilter = CountryFilter.class)
private final USAddress usAddress = new USAddress({line1: "123 MockingBird Ln", country: "US"});
#JsonInclude(value = Include.CUSTOM, valueFilter = CountryFilter.class)
private final ForeignAddress foreignAddress = new ForeignAddress({line1: "123 MockingBird Ln", country: "DE"});
}
public class CountryFilter {
#Override
public boolean equals(Object obj) {
return ...; // This is where I'm having issues. Would like it to do something like obj.getCountry().equals("US").
}
}

I ended up using the #JsonInclude(Include.NON_NULL) and then used a conditional in the constructor to optionally set the address.

Related

Mapping DTO to inherited class

My domain:
public class Moral {
private String moralId;
private String socialReason;
private Framework framework;
}
public class Framework {
private String externalId;
private Set<String> identifiers;
}
public class Lab extends Framework {
private String system;
private String availability;
}
My DTO:
public class CreateLabRequest {
private String socialReason;
private Set<String> identifiers;
private String system;
private String availability;
}
My Mapper for this looks like:
#Mapping(source = "system", target = "framework.system")
#Mapping(source = "availability", target = "framework.availability")
#Mapping(source = "identifiers", target = "framework.identifiers")
Moral createLabRequestToMoral (CreateLabRequest createLabRequest);
However, I get the following error:
Unknown property "system" in type Framework for target name
"framework.system". Did you mean "framework.externalId"? Unknown
property "availability" in type Framework for target name
"framework.availability". Did you mean "framework.externalId"?
Simply, It is not Possible !
Maybe you wanted to make Framework inherits from Map ?!
Otherwise, the problem is due that you want to access some field in a class that doesn't have it !
public class Framework {
private String externalId;
private Set<String> identifiers;
}
public class Lab extends Framework {
private String system;
private String availability;
}
As it says, extends means that your Lab class inherits from Framework, that means that Lab inherits all fields that Framework has, and not the opposite.
So with that being said :
"framework.system" // cannot be accessed
Since there is no field named "system" in the framework class
However :
"lab.externalId" // CAN be accessed
Since Lab class inherits it from its parent class "Framework" eventhough there is no such field named "system" in the Lab class
More explanations about JAVA inheritance can be found here : https://www.geeksforgeeks.org/inheritance-in-java/
This is possible as follows:
#Mapping( source = ".", target = "framework" )
Moral createLabRequestToMoral( CreateLabRequest createLabRequest );
Lab createLabFramework( CreateLabRequest createLabRequest )
Since Lab extends Framework mapstruct will use createLabFramework( CreateLabRequest createLabRequest ) since it is an user defined method.
Also since all the fields are called the same it is not needed to add those #Mapping annotations.
Edit: expanding a bit about the error.
Unknown property "system" in type Framework for target name "framework.system". Did you mean "framework.externalId"? Unknown property "availability" in type Framework for target name "framework.availability". Did you mean "framework.externalId"?
This is basically caused by MapStruct not knowing that there is a Lab class available that extends Framework. MapStruct can also not know that you want to use the Lab class instead of the Framework class.
As shown above, one of the methods is manually defining an additional mapping method to notify MapStruct about this situation.

Sharing a "configuration" between annotations

I want to share a common "configuration" between multiple annotated classes. My initial approach was to point the annotations to a class which then extends the configuration class:
#MyAnnotation(config = SharedConfig.class)
class A {}
#MyAnnotation(config = SharedConfig.class)
class B {}
class SharedConfig extends BaseConfig{
public SharedConfig(){
super("abc",123)
}
}
My initial approach was to find the SharedConfig type during annotation processing and instantiate it to find out the actual config. The problem is I can't instantiate the SharedConfig class during the actual processing...
Any idea how to achieve this?
Indeed, you can't instantiate the config class. But what you can do, is query which annotations are on the config class.
So, you could imagine the following system:
old way:
#Movie(
name = "A Few Good Men",
director = #Director(lastName = "Reiner", firstName = "Rob"),
releaseYear = 1992,
liked = true)
public class Foo {}
#Movie(
name = "A Few Good Men",
director = #Director(lastName = "Reiner", firstName = "Rob"),
releaseYear = 1992,
liked = true)
public class Bar {}
new way:
#Movie(
name = "A Few Good Men",
director = #Director(lastName = "Reiner", firstName = "Rob"),
releaseYear = 1992)
public class Placeholder {
// This class serves solely as a place to store the above annotation.
private Placeholder() {}
}
#Movie(
config = Placeholder.class,
liked = true)
public class Foo {}
#Movie(
config = Placeholder.class,
liked = false)
public class Bar {}
Your rule would presumably be that anything explicitly set on the actual class is taken, and if there is nothing explicitly set but there is a 'config' class set, then the value from the annotation on that config class is taken.
Unfortunately, there's no (non-hacky) way to tell the difference between #Foo and #Foo(value="") when Foo is defined as #interface Foo {String value() default "";} - i.e. there is no way to differentiate an explicit setting of a value that is the same as the default value for a given anno parameter, so you can't actually use 'if you do not explicitly set it, then this defaulting mechanism applies' as a concept in annotations. Therefore, 'use the defaulting mechanism' must be based on the actual value - you need 'stand-in' values that mean: "Inherit from config". That means booleans are right out, unfortunately. You can use enums instead.
Here is an example:
public enum LikeStatus {
LIKED, DISLIKED, INHERIT;
}
// target classes/types
public #interface Movie {
Class<?> config() default Object.class;
LikeStatus liked default LikeStatus.INHERIT;
int releaseYear() default 0;
Director director() default #Director(lastName = "", firstName = "")
String name() default "";
}
and now you need to write some code that knows about the defaults and acts accordingly (so, if name() returns an empty string, that means you should check the config class for the Movie annotation and fetch its name. Same for a release year of 0, a director with a blank first and last name, and so on.

Java :: JacksonXmlProperty :: Multiple fields with same name

I'm extending code from an existing Java class that serializes to and from XML. The existing class is somewhat like this:
#Getter
#JacksonXmlRootElement("element")
public class Element {
#JacksonXmlProperty(localName = "type", isAttribute = true)
private String type;
}
The type field has a finite set of possible values so I created an enum Type with all possible values and (to avoid breaking existing functionality) added a new field to the class, like so:
#Getter
#JacksonXmlRootElement("element")
public class Element {
#JacksonXmlProperty(localName = "type", isAttribute = true)
private String type;
#JacksonXmlProperty(localName = "type", isAttribute = true)
#JsonDeserialize(using = TypeDeserializer.class)
private Type typeEnum;
}
This gives me the following error:
Multiple fields representing property "type": Element#type vs Element#typeEnum
I understand why this is a problem cuz when Jackson would try to serialize the class, two fields in my class map onto the same field in the output XML.
I tried adding a #JsonIgnore on one of the fields and it gets rid of the error but has the side effect of not populating the ignored field either. Is there a way to annotate that a field should be deserialized (while reading XML) but not serialized (while writing XML)?
I really need to keep both fields in the class to not disturb any legacy code that might be using the first field, but at the same time allow newer code to leverage the second field.
Thank you!

Conditionally rename Jackson JSON output

Let's suppose I have a class holding one variable:
#Data
public class Student {
private List<Grade> allGrades;
}
This will be sent to a producer which is written in Python. They would like to have the property in snake_case and we want to keep the camelCase in java.
This can be achieved with Jackson:
#Data
public class Student {
#JsonProperty("all_grades")
private List<Grade> allGrades;
}
Now, we're building an application that can dynamically switch between producers, so some might be written in Java and some in Python.
In our application we can know this by either storing it in the config or sending it as a request header.
If the producer is Java, we would like to refrain from renaming the property. So the main question is:
(How) is it possible to conditionally rename the property allGrades to all_grades based on a String/boolean?
I am sure this is not supported by Jackson out of the box, nor do I think it should be. If you really have to do such a thing, you could subclass your Student class for different producers, such as
#Data
class Student {
private List<Grade> allGrades;
#JsonProperty
List<Grade> getAllGrades() {
return allGrades;
}
}
#Data
#EqualsAndHashCode(callSuper = true)
class PythonStudent extends Student {
#JsonProperty("all_grades")
#Override
public void getAllGrades() {
super.getAllGrades();
}
}
And then construct the desired object based on your condition in a factory class.
That said, it's really easy to consume JSON strings in both Java and Python using either snake_case or camelCase conventions and map them to fields in the desired format. I don't think what you want to achieve adds any value, but it would certainly introduce a lot of complexity.

Cannot with (de)serialize a List with polymorphic items in Jackson

I have an issue (de)serializing JSON that is not defined by me.
Here is some code and JSON that explains the issue:
Code:
public static class Base {
public String klass = "base";
}
public static class SubBase extends Base {
}
public static class Sub1 extends SubBase {
public Sub1() {
klass = "Sub1";
}
}
public static class Sub2 extends SubBase {
public Sub2() {
klass = "Sub2";
}
}
public static class Holder {
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_ARRAY, property="type")
#JsonSubTypes({#JsonSubTypes.Type (name = "sub1", value = Sub1.class),#JsonSubTypes.Type(name = "sub2", value = Sub2.class)})
public List<Base> items = new ArrayList<Base>();
}
Holder holder = new Holder();
holder.items.add(new Sub1());
holder.items.add(new Sub1());
mapper.writeValueAsString(holder);
produces
{"items":[["sub1",{"klass":"Sub1"}],["sub1",{"klass":"Sub1"}]]}
If I change the JsonTypeInfo annotation to
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.WRAPPER_OBJECT, property="type")
produces
{"items":[{"sub1":{"klass":"Sub1"}},{"sub1":{"klass":"Sub1"}}]}
So far, all is good :)
However, the JSON I'm getting from the server has a slightly different structure:
{"type":"sub1", "items":[{"klass":"Sub1"},{"klass":"Sub1"}]}
Where the type of the items array is defined in the "type" property (note that all items in the "items" array are of the same class).
I just cannot figure out which JsonTypeInfo combination to use to make this happen. I tried setting the 'include' to ".EXTERNAL_PROPERTY", but this doesn't work.
Deserializing using this inclusion gives me "Exception in thread "main" org.codehaus.jackson.JsonGenerationException: Can not write a field name, expecting a value" error message.
My question is: Which annotation do I need to use so that '{"type":"sub1", "items":[{"klass":"Sub1"},{"klass":"Sub1"}]}' will fill the 'items' array with all Sub1 instances based on the "type" property of the Holder?
If this is just not possible, is there another way to accomplish this (without the need of an custom serializer for Holder; a custom serializer just for the 'items' array would be fine)?
Thanks!
There is no way to map JSON you show automatically; it is not one of 4 types supported by Jackson.
If you can't make server produce more standard structure (for which automatic support exists), you will need to write a custom deserializer; or to do data-binding in two steps, first into an intermediate easily mappable structure (like JsonNode or Map) and then from that into desired structure manually extracting type, using that (for example, with ObjectMapper.convertValue(object, resultType).

Categories

Resources