I want to generate following xml:
<word start="1556" end="1564" TestArticle="36" Chemical="7">Ammonium</word>
<word start="1566" end="1584" Endpoint="36" Chemical="7" >per-fluorobutyrate</word>
<word start="1585" end="1586" TestArticle="37" >(</word>
I am using following pojo, which takes care of start and end as they are fixed attribute names, but I have "testArticle", "Endpoint", "Chemcial", etc which are dynamic in nature as well as there value, which I am not sure how to handle.
public class WordPOJO {
#JacksonXmlProperty(isAttribute = true)
String start;
#JacksonXmlProperty(isAttribute = true)
String end;
#JacksonXmlText
String str;
// not sure if this is the way to do it, but it not outputing in desired format
#XmlAnyElement(lax = true)
List<String> entityList;
public String getStart() {
return start;
}
public void setStart(String span) {
this.start = span;
}
public String getEnd() {
return end;
}
public void setEnd(String span) {
this.end = span;
}
public List<String> getEntityList()
{
return entityList;
}
public void setEntityList(List<String> entityList)
{
this.entityList = entityList;
}
//#JacksonXmlElementWrapper(useWrapping = false)
//#JacksonXmlProperty(localName = "word")
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
If you know the XML attribute names TestArticle , Chemical and Endpoint in adavance,
then you can represent these as additional properties in your WordPOJO Java class:
#JacksonXmlProperty(isAttribute = true, localName = "TestArticle")
String testArticle;
#JacksonXmlProperty(isAttribute = true, localName = "Chemical")
String chemical;
#JacksonXmlProperty(isAttribute = true, localName = "Endpoint")
String endpoint;
// Getters and setters (omitted here for brevity)
Notice that you also need to explicitly specify the XML names
by localName = "...") to match your XML content.
(See also the javadoc of #JacksonXmlProperty.)
If you would not do this, then Jackson would implicitly
pick up XML names derived from your Java property names
(testArticle, chemical, endpoint) which would not match
your actual XML content.
If the XML attributes are truly dynamic and you don't know them in advance,
then you will need to use #JsonAnyGetter and #JsonAnySetter.
See the javadoc of #JsonAnyGetter and JsonAnySetter.
Don't be irritated by Json in the annotation names. Jackson can handle JSON and XML and many more formats. For JSON you use ObjectMapper.
And for XML you use XmlMapper.
Map<String, Object> otherProperties = new HashMap<>();
#JsonAnySetter
public void setOtherProperty(String name, String value) {
otherProperties.put(name, value);
}
#JsonAnyGetter
public Map<String, Object> getOtherProperties() {
return otherProperties;
}
Related
I have a class in Java that I want to serialize/deserialize to/from XML. Each property of this class should be serialized/deserialized to/from attributes of the XML element.
The XML looks something like:
<element fullName="Asim" age="30" score="0.78" readonly="true" bounds="[0,0][10,20]" tags="tag1,tag2,tag3">
...
...
</element>
If the properties are simple (String, int, boolean), this works. I can simply use #JacksonXmlProperty annotation and it gets the job done:
#JacksonXmlProperty(localName = "fullName", isAttribute = true)
private String fullName;
However, some of the properties are class objects (bounds, list) that I need to convert during serialization/deserialization. I have been able to use #JsonDeserialize annotation to read XML:
#JacksonXmlProperty(localName = "bounds", isAttribute = true)
#JsonDeserialize(using = BoundsDeserializer.class)
private Rectangle bounds;
Serializing these field, on the other hand, is proving to be quite a challenge. I've tried using JsonSerialize(using = BoundsSerializer.class) and JsonSerialize(converter = BoundsConverter.class) and nothing works. Either I get the following exception:
com.fasterxml.jackson.core.JsonGenerationException: Trying to write an attribute when there is no open start element.
Or the resulting XML looks like:
<element fullName="Asim" age="30" score="0.78" readonly="true">
<bounds>
<x>0</x>
<y>0</y>
<width>10</width>
<height>20</width>
</bounds>
<tags tags="tag1" tags="tag2" tags="tag3" />
...
...
</element>
How can I serialize a non-primitive property of a class as a string attribute in XML?
Edit
The main invocation code:
try {
XmlMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String xml = mapper.writeValueAsString(this);
return xml;
}
catch (Exception ex) { ... }
Relevant bits of the class to be serialized/deserialized: The commented lines are alternate approaches I tried (and failed).
#JacksonXmlRootElement(localName = "root")
#JsonIgnoreProperties(ignoreUnknown = true)
#Getter
#EqualsAndHashCode
#ToString
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Slf4j
public class XML {
#JacksonXmlProperty(localName = "text", isAttribute = true)
private String text;
#JacksonXmlProperty(localName = "tags", isAttribute = true)
#JsonDeserialize(using = ListDeserializer.class)
#JsonSerialize(converter = ListConverter.class)
//#JsonSerialize(using = ListSerializer.class)
//#JsonUnwrapped
private List<String> tags;
#JacksonXmlProperty(localName = "bounds", isAttribute = true)
#JsonDeserialize(using = BoundsDeserializer.class)
//#JsonSerialize(using = BoundsSerializer.class, contentAs = String.class)
private Rectangle bounds;
}
Bounds deserializer:
public class BoundsDeserializer extends JsonDeserializer<Rectangle> {
private static final Pattern BOUNDS_PATTERN = Pattern.compile("\\[(-?\\d+),(-?\\d+)]\\[(-?\\d+),(-?\\d+)]");
#Override
#Nullable
public Rectangle deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getValueAsString();
if (value.isBlank()) {
return null;
}
Matcher m = BOUNDS_PATTERN.matcher(value);
if (!m.matches()) {
return ctxt.reportInputMismatch(Rectangle.class, "Not a valid bounds string: '%s'", value);
}
final int x1 = Integer.parseInt(m.group(1));
final int y1 = Integer.parseInt(m.group(2));
final int x2 = Integer.parseInt(m.group(3));
final int y2 = Integer.parseInt(m.group(4));
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
}
List deserializer:
public class ListDeserializer extends JsonDeserializer<List<String>> {
#Override
#Nullable
public List<String> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getValueAsString();
if (value.isBlank()) {
return null;
}
String deBracketed = value.trim().replaceAll("^\\[(.*)]$", "$1");
List<String> listValues = Arrays.stream(deBracketed.split(","))
.map(String::trim)
.filter(Predicate.not(String::isEmpty))
.collect(Collectors.toUnmodifiableList());
return listValues;
}
}
List converter:
public class ListConverter extends StdConverter<List<String>, String> {
#Override
public String convert(List<String> list) {
return String.join(",", list);
}
}
Thank you!
Asim
So the way I hacked my way around this issue was as follows:
#JacksonXmlProperty(localName = "tags", isAttribute = true)
#JsonDeserialize(using = ListDeserializer.class)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private List<String> tags;
#JacksonXmlProperty(localName = "tags", isAttribute = true)
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String tagsAsString() {
return String.join(",", this.tags);
}
The first field is write-only, so it will be deserialized only. The second field is read-only, so it will be serialized only. However, this feels really hackish and I think there has to be a better solution than this. :/
I came up with a better way to do the same thing:
// Used during serialization
#JacksonXmlProperty(localName = "bounds", isAttribute = true)
#JsonSerialize(converter = RectangleToStringConverter.class)
// Used during deserialization
#JsonProperty("bounds")
#JsonDeserialize(converter = StringToRectangleConverter.class)
private Rectangle bounds;
I have a json payload (request payload of a rest api) with a defined schema, but there is one property that can take an array of unknown key value pairs. The value for each property can be of different type like number, string, array, range, date, etc. How do i create a POJO for this property and make deserialization work for the same?
I am currently thinking about writing a custom deserializer for my Property class, where i check the type of value and do some custom logic accordingly.
This looks like a typical requirement. I feel that there should be something available in Jackson or Gson that i am missing. I would love to reuse if it already exist. I looked around in SO, but couldnt find a good answer so far. Any suggestions would be appreciated.
{
"id": 1234,
"name": "test name 1",
"properties": [
{
"key_a": 100
},
{
"key_b": [
"string1",
"string2",
"string3"
]
},
{
"key_c": {
"range": {
"min": 100,
"max": 1000
}
}
}
]
}
I am thinking my POJO for property object would look something like this.
class Property {
private String key;
private Value value;
}
It is possible to use inheritance for that. This is the classes for your example with Jackson
public class Sample {
#JsonProperty(value = "id")
Integer id;
#JsonProperty(value = "name")
String name;
#JsonProperty(value = "properties")
List<Property> properties;
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#JsonSubTypes.Type(value = KeyA.class, name = "key_a"),
#JsonSubTypes.Type(value = KeyB.class, name = "key_b"),
#JsonSubTypes.Type(value = KeyC.class, name = "key_c")
})
public abstract class Property {
}
public class KeyA extends Property{
Integer value;
public KeyA(Integer value) {
this.value = value;
}
#JsonValue
public Integer getValue() {
return value;
}
}
public class KeyB extends Property {
List<String> valueList;
#JsonCreator
public KeyB( List<String> valueList) {
this.valueList = valueList;
}
#JsonValue
public List<String> getValueList() {
return valueList;
}
}
public class KeyC extends Property {
#JsonProperty(value = "range")
Range value;
}
public class Range {
#JsonProperty(value = "min")
Integer min;
#JsonProperty(value = "max")
Integer max;
}
If I understand correctly you want to change to JSON and back. I wrote a small class for my own SpringBoot project, using ObjectMapper
#Component
public final class JsonUtils {
private final ObjectMapper mapper;
#Autowired
public JsonUtils(ObjectMapper mapper) {
this.mapper = mapper;
}
public String asJsonString(final Object object) {
try {
return mapper.registerModule(new JavaTimeModule())
.writeValueAsString(object);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*
* Customized Objectmapper for reading values compatible with this class' other methods
* #Return the desired object you want from a JSON
* IMPORTANT! -your return object should be a class that has a #NoArgsConstructor-
*/
public Object readValue(final String input, final Class<?> classToRead) {
try {
return mapper
.readValue(input, classToRead);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}`
Perhaps it can be of some use to you.
I'm not getting my "announcements" when I try to parse messages like these:
<?xml version="1.0" encoding="UTF-8"?>
<message messageType="SUBSCRIBER" messageName="ANNOUNCEMENT">
<announcement time="12:30">
Lunchtime!
</announcement>
<announcement time="32:00">
Good night ...
</announcement>
<errorText>Phone number missing, subscriber: Dick</errorText>
</message>
The JAXB Java class seemed pretty straightforward. And is based on similar messages that work. In this case the difference seems to be that I can have two different types of nested element (XmlElement) in the main block.
The code below actually parses the XML but it doesn't call the Announcement.setMessageText() method. Whereas I can see that the time attribute is set from the XML and I have an array size=2 for announcements, it reads and sets the single errorText XmlElement. Btw, I also removed the tag from the code and XML -- Not change in treatment of my messageText. Ideas welcome!
#XmlRootElement(name = "message")
public class AnnouncementMessage
{
#XmlAttribute
public String getMessageName(){
return this.messageName;
}
public void setMessageName( String name ){
this.messageName = name;
}
#XmlAttribute
public String getMessageType(){
return messageType;
}
public void setMessageType( String newMessageType ){
this.messageType = newMessageType;
}
#XmlElement(name = "errorText")
public String getErrorText(){
return errorText;
}
public void setErrorText( String newMsg ){
this.errorText = newMsg;
}
private List<Announcement> announcements = new ArrayList<>();
#XmlElement(name = "announcement")
public List<Announcement> getAnnouncements(){
return this.announcements;
}
public void setAnnouncements( List<Announcement> newAnnouncements ){
this.announcements = newAnnouncements;
}
}
And the Announcement class:
#XmlRootElement(name = "announcement")
public class Announcement
{
private String messageText = "";
private String time = "12:00";
XmlAttribute(name ="time")
public String getTime(){
return this.time;
}
public void setTime( String newMsg ){
this.messageText = time;
}
#XmlElement(name="announcement")
public String getMessageText(){
return this.messageText;
}
public void setMessageText( String newMsg ){
this.messageText = newMsg;
}
Announcement(){
}
}
Using the name parameter in the for the XmlElement seem to make no difference. My thanks in advance.
Use the #XmlValue annotation for the message text:
#XmlValue
public String getMessageText(){
return this.messageText;
}
Additionally, as noted in this related answer by OP, an #XmlAccessorType(XmlAccessType.NONE) is needed on the Announcement class if it contains getter/setter pairs that are not mapped to/from XML and don't have an #XmlTransient annotation.
I have a situation where I am passing back n forth an object from Java to Javascript client and it's being serialized by the built in Jackson mapper in Spring 3 (using the #RequestBody / #ResponseBody and application/json content type)
The problem I have is some classes implement from an interface which has a getter but no setter.
I do want the getter value available from the client side so I cannot use #JsonIgnore annotation because then it ignores the property entirely, both serializing and deserializing. I need the property when serialized.
Any other way to do this?
There is probably an easier way, but I thought to mention the usage of JSON views as a possible solution. There's an example on this thread.
You might need a different view on deserialization and not just on serialization, and that would be a Jackson 2.0 feature - supported by Spring 3.2 and backported into Spring 3.1. Using a view on serialization only is a feature since Jackson 1.4.
Another option that comes to mind is using a custom deserializer.
I was looking for the same thing.
According to the Jackson docs, we can set a #JsonIgnore annotation on a property, getter or setter and that would hide the complete property, unless we give the setter a #JsonProperty annotation that would make sure this property is visible when setting it. Unfortunately, that doesn't work the other way around. I wanted to show the property in my response, but not include it in my request.
Use case is an auto generated id for example. I don't want the user to set, or even see that, but he may read it.
You can achieve this by hiding the attribute itself indeed and then add a #JsonAnyGetter method. In this method you can massage the data to any form you want. It's also useful to represent complex attribute classes of which you only want to show a single name or identifier or other formatting. There are better ways of doing the latter, but if the use case is not too complex, this suffices.
So as example (My apologies if this is too elaborate):
User:
public class User {
private String uid;
private String customerId;
private String taxSsnId;
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getCustomerId() {
return extCustomerId;
}
public void setCustomerId(String extCustomerId) {
this.extCustomerId = extCustomerId;
}
public String getTaxSsnId() {
return taxSsnId;
}
public void setTaxSsnId(String taxSsnId) {
this.taxSsnId = taxSsnId;
}
#JsonIgnore
public void getId(){
if (getUid() != null){
return getUid();
}
if (getCustomerId() != null ){
return getCustomerId();
}
return null;
}
}
Setting:
public class Setting {
#JsonIgnore
private int id;
private String key;
private String value;
#JsonIgnore
private User lastUpdatedBy;
#JsonIgnore
private Date lastUpdatedAt;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public User getLastUpdatedBy() {
return lastUpdatedBy;
}
public void setLastUpdatedBy(User lastUpdatedBy) {
this.lastUpdatedBy = lastUpdatedBy;
}
public Date getLastUpdatedAt() {
return lastUpdatedAt;
}
public void setLastUpdatedAt(Date lastUpdatedAt) {
this.lastUpdatedAt = lastUpdatedAt;
}
#JsonAnyGetter
private Map<String, String> other() {
Map<String, String> map = new LinkedHashMap<String, String>();
map.put( "id", this.getId());
map.put( "lastUpdatedBy", this.getLastUpdatedBy().getId());
SimpleDateFormat format = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z");
map.put( "lastUpdatedAt", format.format(this.getLastUpdatedAt()) );
return map;
}
}
Yields this Request schema (de-serialization view):
{
"key": "string",
"value": "string"
}
and this Response schema (serialized view):
{
"key": "string",
"value": "string",
"id": "12345",
"lastUpdatedBy": "String",
"lastUpdatedAt": "String"
}
I'm trying to use JAXB annotations with RestEasy in order to choose names and elements order in my JSON output.
Somehow, it isn't working, even if the RestEasy doc says it's possible.
Here some code:
#XmlRootElement(name = "translation")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "translation", propOrder = {
"key",
"value"
})
public class TranslationDTO {
public TranslationDTO() {}
public TranslationDTO(Translation translation) {
setKey(translation.getTranslationKey().getValue());
setValue(translation.getContent());
//setCreationDate(translation.getCreatedTimestamp());
}
#XmlElement(name = "key")
private String key;
#XmlElement(name = "value")
private String value;
//private Date creationDate;
#XmlElement(name = "key")
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
#XmlElement(name = "value")
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
/*#XmlElement(name = "creationDate")
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}*/
}
And here an example output:
{
"name":"i18nhelp",
"currentVersion":"1",
"currentTotalKeys":28,
"oldTotalKeys":0,
"totalLanguages":2,
"languageDtos":[{
"name":"Anglais",
"iso639":"en",
"totalCurTrans":28,
"newCurTrans":28,
"oldTrans":0
},
{
"name":"Français",
"iso639":"fr",
"totalCurTrans":28,
"newCurTrans":28,
"oldTrans":0
}]
}
The JAXB annotations don't seem to be taken in account at all.
Any idea will be considered...
If you are using JBoss (or WildFly as it's now called) as an application server, you may be experiencing RestEasy using the Jackson (http://jackson.codehaus.org/) JSON marshaller, which has its own annotations – you can find the documentation linked from Jackson's homepage. They are a bit more expressive than "just" JAXB, you may want to consider them if you specifically target JSON output only.
If you would rather only use JAXB, as you example indicates, you can switch from Jackson to something different by specifying which resteasy provider module you want to use in a jboss-deployment-structure.xml, as detailed in this answer.