MOXy adds type and uses toString? - java

Disclaimer: I'm new to all this, so my terminology may be wrong
I've got some Java POJO's I want to serialize to JSON & XML. I'm using MOXy 2.5.0 for JSON and Jersey 2.4.1.
#XmlRootElement
class Root {
// #XmlElements({#XmlElement(name = "destination_address", type = LatLong.class),
// #XmlElement(name = "destination_address", type = Polygon.class)})
public Object[] destination_addresses;
}
public class LatLong {
public double lat, lng;
}
public class Polygon {
protected List<LatLong> points = new ArrayList<LatLong>();
#XmlElements({#XmlElement(name = "lat", type = Lat.class),
#XmlElement(name = "lng", type = Lng.class)})
private LatOrLong[] getLatOrLongs() {
LatOrLong[] retval = new LatOrLong[points.size() * 2];
for (int point = 0; point < points.size(); point++) {
LatLong latLong = points.get(point);
retval[point * 2] = new Lat(latLong.lat);
retval[point * 2 + 1] = new Lng(latLong.lng);
}
return retval;
}
static abstract private class LatOrLong {
#XmlValue
private double latOrLong;
private LatOrLong() {}
private LatOrLong(double latOrLong) {this.latOrLong = latOrLong;}
}
static private class Lat extends LatOrLong {
private Lat() {}
private Lat(double lat) {super(lat);}
}
static private class Lng extends LatOrLong {
private Lng() {}
private Lng(double lng) {super(lng);}
}
}
This doesn't work in XML with the two lines commented out, but in JSON, MOXy is adding a type: latLong attribute to the destination_addresses array, as well as using the toString() method of Polygon.
How can I hide the type?
How can I get MOXy to use getLatOrLongs() instead of toString()?
EDIT: I've simplified Polygon to just serialize points and changed destination_addresses to be a List<Object> instead of Object[] .

The pojo mapping feature is by default enabled using MOXy.
But if for any case, you need to implement a specific marshal/unmarshal (I take here ObjectID from MongoDB which is a t:
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.bson.types.ObjectId;
public class ObjectIdXmlAdapter extends XmlAdapter<String, ObjectId> {
#Override
public String marshal(ObjectId id) throws Exception {
if(id == null) {
return null;
} else {
return id.toString();
}
}
#Override
public ObjectId unmarshal(String id) throws Exception {
return new ObjectId(id);
}
}
And then, on your POJO:
#XmlJavaTypeAdapter(ObjectIdXmlAdapter.class)
public ObjectId getId() {
return id;
}
Will serialize your id element as expected...
Hope this helps, this should be the main trouble.

Related

Using #JsonCreator to create two instances of same class in one JSON DTO

I would like to deserialize JSON of this structure:
{
"employee_pricing_type":"COMPUTE_BY_OWN_RATE",
"employee_rate":10,
"customer_pricing_type":"COMPUTE_BY_OWN_RATE",
"customer_rate":200
}
I have such POJO to create price setting from a HTTP request:
public class ObjectPricingSetting {
#JsonProperty("pricing_type") // describes output
private final ObjectPricingType pricingType;
#JsonProperty("own_rate") // describes output
private final BigDecimal ownRate;
public ObjectPricingSetting(final ObjectPricingType pricingType, final BigDecimal ownRate) {
AssertUtils.notNull(pricingType, "pricingType");
this.pricingType = pricingType;
if (ownRate != null) {
AssertUtils.isGtZero(ownRate, "ownRate");
this.ownRate = ownRate;
} else {
this.ownRate = null;
}
}
public ObjectPricingType getPricingType() {
return pricingType;
}
public BigDecimal getOwnRate() {
return ownRate;
}
}
this is DTO:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ObjectPricingCommand extends BaseDto<ObjectId> {
#JsonProperty(value = "employee_pricing_setting")
private ObjectPricingSetting employeePricingSetting;
#JsonProperty(value = "customer_pricing_setting")
private ObjectPricingSetting customerPricingSetting;
}
I would like to create these two instances of ObjectPricingSetting with #JsonCreator.
Q: How should I anotate #JsonProperty parameter in ObjectPricingSetting constructor to recognize what JSON value should use to create these two instances?
You can use #JsonUnwrapped with a prefix in your parent class:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ObjectPricingCommand extends BaseDto<ObjectId> {
#JsonUnwrapped(prefix = "employee_")
private ObjectPricingSetting employeePricingSetting;
#JsonUnwrapped(prefix = "customer_")
private ObjectPricingSetting customerPricingSetting;
}
Then you can use the normal #JsonCreator/#JsonProperty in your nested DTO, without the prefix:
public class ObjectPricingSetting {
#JsonCreator
public ObjectPricingSetting(
#JsonProperty("pricing_type") final ObjectPricingType pricingType,
#JsonProperty("rate") final BigDecimal ownRate) {
...

How to override the #AdminPresentation for existing attributes [Broadleaf Commerce]

I am trying to override the #AdminPresentation of the following attribute defined in ProductImpl:
#Column(name = "DISPLAY_TEMPLATE")
#AdminPresentation(friendlyName = "ProductImpl_Product_Display_Template",
group = GroupName.Advanced)
protected String displayTemplate;
Currently, it is displayed as a text field by default as there is no fieldType attribute provided. But I want to display a dropdown select menu with predefined values such as Product and Plan. Here is what I've tried so far:
I've created a class DisplayTemplateType that implements BroadleafEnumerationType and defined PLAN and PRODUCT enums. Here is the code of that class:
public class DisplayTemplateType implements Serializable, BroadleafEnumerationType {
private static final long serialVersionUID = 7761108654549553693L;
private static final Map<String, DisplayTemplateType> TYPES = new LinkedHashMap<String, DisplayTemplateType>();
public static final DisplayTemplateType PLAN = new DisplayTemplateType("PLAN", "PLAN");
public static final DisplayTemplateType PRODUCT = new DisplayTemplateType("PRODUCT", "PRODUCT");
public static DisplayTemplateType getInstance(final String type) {
return TYPES.get(type);
}
private String type;
private String friendlyType;
public DisplayTemplateType() {
//do nothing
}
public DisplayTemplateType(final String type, final String friendlyType) {
this.friendlyType = friendlyType;
setType(type);
}
#Override
public String getType() {
return type;
}
#Override
public String getFriendlyType() {
return friendlyType;
}
private void setType(final String type) {
this.type = type;
if (!TYPES.containsKey(type)) {
TYPES.put(type, this);
} else {
throw new RuntimeException("Cannot add the type: (" + type + "). It already exists as a type via " + getInstance(type).getClass().getName());
}
}
// equals() and hashCode() implementation is removed for readability
}
Then in applicationContext-admin.xml file, I have added the following override properties:
<mo:override id="blMetadataOverrides">
<mo:overrideItem ceilingEntity="org.broadleafcommerce.core.catalog.domain.Product">
<mo:field name="displayTemplate">
<mo:property name="explicitFieldType" value="BROADLEAF_ENUMERATION"/>
<mo:property name="broadleafEnumeration" value="com.community.core.domain.DisplayTemplateType"/>
</mo:field>
</mo:overrideItem>
</mo:override>
But it didn't change anything. Am I missing something here?
Finally, after trying many things, I came up with a workaround. Instead of going with the XML based approach, I had to extend the ProductImpl class to override #AdminPresentation of its attributes. But for extending I needed to define an #Entity and as a result, I needed to create a useless table to bind to that entity. I know this is not the perfect approach but I couldn't find any better solution for this. Here is my code, so that someone might get help from it in the future:
#Entity
#Immutable
#AdminPresentationMergeOverrides({
#AdminPresentationMergeOverride(name = "displayTemplate", mergeEntries = {
#AdminPresentationMergeEntry(propertyType = PropertyType.AdminPresentation.FIELDTYPE, overrideValue = "BROADLEAF_ENUMERATION"),
#AdminPresentationMergeEntry(propertyType = PropertyType.AdminPresentation.BROADLEAFENUMERATION, overrideValue = "com.community.core.domain.DisplayTemplateType"),
#AdminPresentationMergeEntry(propertyType = PropertyType.AdminPresentation.REQUIREDOVERRIDE, overrideValue = "REQUIRED"),
#AdminPresentationMergeEntry(propertyType = PropertyType.AdminPresentation.DEFAULTVALUE, overrideValue = "PLAN")
})
})
public class CustomProduct extends ProductImpl {
private static final long serialVersionUID = -5745207984235258075L;
}
This is how it is displayed now:

JAXB - TreeGrid solve conflict XmlElement

I have this classes structure to serialize TreeGrid (www.treegrid.com) object:
DataGrid.java
#XmlRootElement(name = "Grid")
public class DataGrid implements Serializable {
private static final long serialVersionUID = 337286974296229101L;
#XmlElement(name = "Body")
public DataGridData data = new DataGridData();
#XmlElement(name = "IO")
public XmlAttributeHolder io = new XmlAttributeHolder();
public DataGrid() {
}
}
and
DataGridData.java
public class DataGridData {
#XmlElement(name="B")
public DataGridCurrentPage currentPage = new DataGridCurrentPage();
#XmlElement(name="B")
public List<XmlAttributeHolder> pageList = new ArrayList<XmlAttributeHolder>();
}
These classes will be processed to return an XML structure as follow:
<Grid>
<Body>
<B />
</Body>
</Grid>
but the information encapsuled in B can be different (so exist two properties in DataGridData class mapped by the same XmlElement).
If I run my project under Java 7 that's all OK but with Java 8 is raise an exception about conflict two properties can't use the same XmlElement map.
A possible solution is: Encapsule two properties in two different classes as follow:
DataGridData.java
public class DataGridData {
private DataGridDataCP dataGridDataCP;
private DataGridDataPL dataGridDataPL;
public DataGridData() {
this.dataGridDataCP = new DataGridDataCP();
this.dataGridDataPL = new DataGridDataPL();
}
public DataGridDataCP getDataGridDataCP() {
return dataGridDataCP;
}
public void setDataGridDataCP(DataGridDataCP dataGridDataCP) {
this.dataGridDataCP = dataGridDataCP;
}
public DataGridDataPL getDataGridDataPL() {
return dataGridDataPL;
}
public void setDataGridDataPL(DataGridDataPL dataGridDataPL) {
this.dataGridDataPL = dataGridDataPL;
}
}
DataGridDataCP.java
public class DataGridDataCP {
private DataGridCurrentPage currentPage;
public DataGridDataCP() {
this.currentPage = new DataGridCurrentPage();
}
#XmlElement(name="B")
public DataGridCurrentPage getCurrentPage() {
return currentPage;
}
public void setCurrentPage(DataGridCurrentPage currentPage) {
this.currentPage = currentPage;
}
}
DataGridDataPL.java
public class DataGridDataPL {
private List<XmlAttributeHolder> pageList;
public DataGridDataPL() {
this.pageList = new ArrayList<XmlAttributeHolder>();
}
#XmlElement(name="B")
public List<XmlAttributeHolder> getPageList() {
return pageList;
}
public void setPageList(List<XmlAttributeHolder> pageList) {
this.pageList = pageList;
}
}
But in this way when DataGridData class has been serialized, add a tag <dataGridDataCP> (or <dataGridDataPL>) but I don't want to show this intermediate tag.
I've tried with XmlAccessorType annotation to exclude object DataGridDataCP / DataGridDataPL but this annotation exclude the complete object (with its encapsuled properties and no only the property in DataGridData class)
You can try something, but you will be able only to serialize properly. Deserialization will not work, and I will explain why. The code would look something like this:
#XmlAccessorType(XmlAccessType.FIELD)
public class DataGridData {
#XmlElements({
#XmlElement(name = "B", type = DataGridCurrentPage.class),
#XmlElement(name = "B", type = XmlAttributeHolder.class),
})
List<Object> values;
}
Now if you create your POJOs and serialize you will get the XML you need. If this is your need then you should be fine.
But if you want also to deserialize, since both classes are mapped into #XmlElement with name 'B' it will pick one of the target classes for deserialization (I believe the last specified).

Dynamic binding of JsonProperty during deserialization

I'm wondering if there is any legit way to dynamically allocate name of JsonProperty so I would change it over time when needed ? With that being said I mean having :
#JsonIgnoreProperties(ignoreUnknown = true)
public class Record
{
public String Name;
#JsonIgnoreProperties(ignoreUnknown = true)
public static class QueryResult<T>
{
public List<T> records;
}
public static class QueryResultRecord extends QueryResult<Record>
{
}
}
Like above, I have a property Name, which by default will be named "Name" like this:
[
{
Name: "Test",
},
{
Name: "test",
},
]
Even though I have flexibility to use #JsonProperty("name") that's not a solution. What I am after is changing it multiple times when needed as I have some parameterized query which relies on it. So I would like to have Name, FirstName, LastName and so on. Is refletion api the right thing to use it here ?
The easiest legit way is to write custom AnnotationIntrospector:
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
public class MyJacksonAnnotationIntrospector extends JacksonAnnotationIntrospector
{
private static final long serialVersionUID = 1L;
#Override
public PropertyName findNameForSerialization(Annotated a) {
PropertyName pn = super.findNameForSerialization(a);
if (pn.getSimpleName().equals("Name")) {
return pn.withSimpleName("LastName"); // set property name to your heart's content...
}
return pn;
}
}
and then pass it to the jackson mapper:
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new MyJacksonAnnotationIntrospector());
Record r1 = new Record();
mapper.writeValue(System.out, r1);
Note: the same introspector is used during deserialization.
I didn't found any simple way to do it but you can use a custom JsonSerializer and implement your logic in it :
// Record class
#JsonIgnoreProperties(ignoreUnknown = true)
public class Record {
protected String name;
public Record(String name) {
this.name = Name;
}
// ...
}
// RecordJsonSerializer class
public static class RecordJsonSerializer extends JsonSerializer<Record> {
private static final String[] NAMES = new String[]{
"Name",
"FirstName"
// ...
};
protected int idx;
public RecordJsonSerializer() {
idx = 0;
}
#Override
public void serialize(Record r, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonProcessingException {
jg.writeStartObject();
jg.writeObjectField(NAMES[idx++], r.name); // Change the field name
jg.writeEndObject();
}
}
// Use case
Record[] records = new Record[]{
new Record("r0"),
new Record("r1")
};
ObjectMapper mapper = new ObjectMapper()
.registerModule(
new SimpleModule("Record")
.addSerializer(Record.class, new RecordJsonSerializer())); // Register the serializer instance
System.out.println(mapper.writeValueAsString(records));
The output of this is: [{"Name":"r0"},{"FirstName":"r1"}]
Of course you must change the logic to define the property name to use when serializing the object (mine will crash with 3 records but it's just a simple example).

Jackson mapping Object or list of Object depending on json input

I have this POJO :
public class JsonObj {
private String id;
private List<Location> location;
public String getId() {
return id;
}
public List<Location> getLocation() {
return location;
}
#JsonSetter("location")
public void setLocation(){
List<Location> list = new ArrayList<Location>();
if(location instanceof Location){
list.add((Location) location);
location = list;
}
}
}
the "location" object from the json input can be either a simple instance of Location or an Array of Location. When it is just one instance, I get this error :
Could not read JSON: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
I've tried to implement a custom setter but it didn't work. How could I do to map either a Location or a List depending on the json input?
Update: Mher Sarkissian's soulution works fine, it can also be used with annotations as suggested here, like so:.
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<Item> item;
My deepest sympathies for this most annoying problem, I had just the same problem and found the solution here: https://stackoverflow.com/a/22956168/1020871
With a little modification I come up with this, first the generic class:
public abstract class OptionalArrayDeserializer<T> extends JsonDeserializer<List<T>> {
private final Class<T> clazz;
public OptionalArrayDeserializer(Class<T> clazz) {
this.clazz = clazz;
}
#Override
public List<T> deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
ArrayList<T> list = new ArrayList<>();
if (node.isArray()) {
for (JsonNode elementNode : node) {
list.add(oc.treeToValue(elementNode, clazz));
}
} else {
list.add(oc.treeToValue(node, clazz));
}
return list;
}
}
And then the property and the actual deserializer class (Java generics is not always pretty):
#JsonDeserialize(using = ItemListDeserializer.class)
private List<Item> item;
public static class ItemListDeserializer extends OptionalArrayDeserializer<Item> {
protected ItemListDeserializer() {
super(Item.class);
}
}
This is already supported by jackson
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

Categories