Jackson XML custom field deserializer - java

When using Jackson for JSON processing I extended JsonDeserializer<T> and was able to handle custom deserialization. Is there a similar thing for processing XML with Jackson?
import java.util.List;
public class Something {
private int iinteger;
private String sstring;
private List<String> sarray;
public Something(int iinteger, String sstring, List<String> sarray) {
this.iinteger = iinteger;
this.sstring = sstring;
this.sarray = sarray;
}
public Shit() {
}
public int getIinteger() {
return iinteger;
}
public void setIinteger(int iinteger) {
this.iinteger = iinteger;
}
public String getSstring() {
return sstring;
}
public void setSstring(String sstring) {
this.sstring = sstring;
}
public List<String> getSarray() {
return sarray;
}
public void setSarray(List<String> dumb) {
this.sarray = dumb;
}
}
I want to deserialize from
<?xml version="1.0"?>
<Something xmlns="">
<iinteger>23</iinteger>
<sstring>hey</sstring>
<sarray >abc abd abv</sarray >
</Something>

Besides JSON, Jackson also de/serializes from/to XML.
You'll need the following dependency:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.6.3</version>
</dependency>
In addition to standard Jackson annotations and optional JAXB annotations (javax.xml.bind.annotation), Jackson provides some XML-specific annotations.
Consider the following POJO as example:
public class Simple {
public int x = 1;
public int y = 2;
}
You can serialize it to XML using:
ObjectMapper xmlMapper = new XmlMapper();
String xml = xmlMapper.writeValueAsString(new Simple());
The result is:
<Simple>
<x>1</x>
<y>2</y>
</Simple>
To deserialize:
ObjectMapper xmlMapper = new XmlMapper();
Simple value = xmlMapper.readValue("<Simple><x>1</x><y>2</y></Simple>", Simple.class);

Yes, there is JAXB.
Intro to JAXB
The reference implementation is called Project Kenai.
It's part of Metro (JAX-WS reference implementation), but it can be used standalone.

Related

JaxbDto Serialization and deserialization

I need to receive some message with SOAP so I've generated a few classes by xsd-scheme and maven-jaxb2-plugin like this:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Claim", propOrder = {
"field",
})
public class ClaimType {
#XmlElement(required = true, type = Integer.class, nillable = false)
protected Integer field;
public Integer getField() {
return bpType;
}
public void setField(Integer value) {
this.field= value;
}
}
After receiving message I need to send these to the next one microservice in wrap of HashMap.
I supposed to use ObjectMapper to convert:
//JAXB DTO --> JSON
ObjectMapper objectMapper = new ObjectMapper();
String jsonContent = objectMapper.writeValueAsString(claimType);
map.put("json", jsonContent);
//JSON --> JAXB DTO
ObjectMapper objectMapper = new ObjectMapper();
String json = map.get("json");
ClaimType claimType = objectMapper.readValue(json, ClaimType.class);
But the generated classes are haven't any constructors so I got the exception like "
No creator like default constructor are exists".
What is the best preactice to work with Jaxb Dto? Can I do smth to successful convert these json to object? Thanks in advance!
I've solved my problem by using ObjectMapper MixIn:
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
#JsonIgnoreProperties(value = {"globalScope", "typeSubstituted", "nil"})
public abstract class JAXBElementMixIn<T> {
#JsonCreator
public JAXBElementMixIn(#JsonProperty("name") QName name,
#JsonProperty("declaredType") Class<T> declaredType,
#JsonProperty("scope") Class scope,
#JsonProperty("value") T value) {
}
}
And the convertation:
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(JAXBElement.class, JAXBElementMixIn.class);
solution link

Jackson deserialize GeoJson Point in Spring Boot

I have a #Entity model that has a property of type com.vividsolutions.jts.geom.Point. When I try to render this model in a #RestController I get a recursion exception.
(StackOverflowError); nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Infinite
recursion (StackOverflowError) (through reference chain:
com.vividsolutions.jts.geom.Point[\"envelope\"]-
>com.vividsolutions.jts.geom.Point[\"envelope\"]....
The entity looks like this (shortened for brevity):
#Entity
#Data
public class MyEntity{
// ...
#Column(columnDefinition = "geometry")
private Point location;
// ...
}
After some research I found out that this is because Jackson cannot deserialize GeoJson by default. Adding this library should solve the issue: https://github.com/bedatadriven/jackson-datatype-jts.
I am now not sure how to include this module in the object mapper in spring boot. As per documentation in boot, I tried adding it to the #Configuration in the following two ways:
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.modulesToInstall(new JtsModule());
return builder;
}
and
#Bean
public JtsModule jtsModule(){
return new JtsModule();
}
Both didn't remove the exception. Sry if this is a duplicate, but all I was able to find SO were customising the ObjectMapper which in my understanding of the documentation is no the "spring boot way".
As a workaround I am #JsonIgnoreing the Point and have custom getters and setters for a non existent coordinated object,... but it's not the way I'd like to keep it.
As of 2020 most of the JTS libraries are outdated and no longer work. I found one fork on Maven Central that was updated recently and it worked flawlessly with jackson-core:2.10.0 and jts-core:1.16.1:
implementation 'org.n52.jackson:jackson-datatype-jts:1.2.4'
Sample usage:
#Test
void testJson() throws IOException {
var objectMapper = new ObjectMapper();
objectMapper.registerModule(new JtsModule());
GeometryFactory gf = new GeometryFactory();
Point point = gf.createPoint(new Coordinate(1.2345678, 2.3456789));
String geojson = objectMapper.writeValueAsString(point);
InputStream targetStream = new ByteArrayInputStream(geojson.getBytes());
Point point2 = objectMapper.readValue(targetStream, Point.class);
assertEquals(point, point2);
}
You don't need to use any annotations on class fields or register new Spring Beans, just register the JTS module with Jackson.
Maybe you should tag your geometric attribute with #JsonSerialize and #JsonDeserialize. Like this:
import com.bedatadriven.jackson.datatype.jts.serialization.GeometryDeserializer;
import com.bedatadriven.jackson.datatype.jts.serialization.GeometrySerializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.vividsolutions.jts.geom.Geometry;
import fr.info.groloc.entity.json.GreffeDeserializer;
import javax.persistence.Entity;
#Entity
public class Table
{
#JsonSerialize(using = GeometrySerializer.class)
#JsonDeserialize(contentUsing = GeometryDeserializer.class)
private Geometry coord;
// ...
}
If you are using Spring-Boot you only need for:
import com.bedatadriven.jackson.datatype.jts.JtsModule;
// ...
#Bean
public JtsModule jtsModule()
{
return new JtsModule();
}
As Dave said you need to add this dependency to your pom.xml:
<dependency>
<groupId>com.bedatadriven</groupId>
<artifactId>jackson-datatype-jts</artifactId>
<version>2.4</version>
</dependency>
The above workaround using JTSModule results in an internal SpringBoot error for me. I was able to solve this issue by making sure the getter methods of my Entity are returning String types.
#Entity
public class MyClassWithGeom {
#Id
#GeneratedValue
private Long id;
private Point centre;
private Polygon boundary;
private MyClassWithGeom() {}
public MyClassWithGeom(String centreLat, String centreLng, Double[]... boundaryCoords) {
String wkt = "POINT (" + centreLat + " " + centreLng + ")";
StringBuilder builder = new StringBuilder("POLYGON (( ");
for(int i=0;i<boundaryCoords.length;i++) {
Double[] coord = boundaryCoords[i];
if (i < boundaryCoords.length - 1)
builder = builder.append(coord[0]).append(" ").append(coord[1]).append(", ");
else
builder = builder.append(coord[0]).append(" ").append(coord[1]).append(" ))");
}
try {
this.centre = (Point) this.wktToGeometry(wkt);
logger.info(this.centre.toString());
this.boundary = (Polygon) this.wktToGeometry(builder.toString());
logger.info(this.boundary.toString());
}
catch (ParseException pe) {
logger.error(pe.getMessage());
logger.error("Invalid WKT: " + wkt);
}
}
public Geometry wktToGeometry(String wellKnownText) throws ParseException {
return new WKTReader().read(wellKnownText);
}
public String getCentre() { return centre.toString(); }
public String getName() { return name; }
public String getBoundary() { return boundary.toString(); }
}
When I'm dealing with spring boot spatial data types in spring boot, com.vividsolutions.jts.geom.Point raised a lot of issues for me. Currently, I'm using Point of type
org.locationtech.jts.geom.Point
which works like a charm
Please try to make changes as below and try again..
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Change the model like below.
#Entity
#Data
public class MyEntity{
// ...
#Column(columnDefinition = "geometry")
#JsonDeserialize(as = Point.class)
private Point location;
// ...
}
In Case aboveconfiguration does not work with your JacksonSerializer class, please try below once.
public class JacksonSerializer {
private JacksonSerializer(){
}
private static final ObjectMapper objectMapper = new ObjectMapper();
private static boolean isInit = false;
private static void init() {
if (isInit == false) {
objectMapper.setDefaultPropertyInclusion(Include.NON_EMPTY);
objectMapper.disable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.setDateFormat(new ISO8601DateFormat());
objectMapper.setAnnotationIntrospector(new JsonIgnoreIntrospector());
isInit = true;
}
}

Jaxb annotation #xmlelement(name="ElementName") not binding with xml element name

My Spring MVC Web Service code is as follows.
Model Class
#XmlRootElement(name="wrappedSecretData")
public class VendorData {
private long lKeyId;
#XmlElement(name="keyId")
public long getlKeyId() {
return lKeyId;
}
public void setlKeyId(long lKeyId) {
this.lKeyId = lKeyId;
}
}
Controller Method
#RequestMapping(value = "/vendor", method = RequestMethod.POST)
public String addVendor(#RequestBody VendorData vendorData) {
/*Checking recieved value*/
System.out.println(vendorData.getlKeyId());//**Returning 0 value **
return "Success";
}
Xml request body for web service
<wrappedVendorSecretsMetadata>
<keyId>1</keyId>
</wrappedVendorSecretsMetadata>
I am getting "0" value in lKeyId(Bold comment).
Where am I doing wrong.
Please provide the correct way to bind the xml element to object member using #XmlElement(name="keyId") annotation.
I think you need the #XmlElement only over the variable declaration.
try this:
#XmlRootElement(name="wrappedVendorSecretsMetadata")
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
public class VendorData {
private long lKeyId;
public VendorData(){
}
#XmlElement(name="keyId")
public long getlKeyId() {
return lKeyId;
}
public void setlKeyId(long lKeyId) {
this.lKeyId = lKeyId;
}
}
By default, annotations doesn't work with XmlMapper in jaxb. You have to register the annotation module for this purpose as I have done in the following code block:
String xmlData = getMyXmlData();
ObjectMapper objectMapper = new XmlMapper();
objectMapper.registerModule(new JaxbAnnotationModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MyClass myObj= objectMapper.readValue(xmlData , MyClass.class);
In your case, you have to overwrite the Xml to Object binding process. To do that, you can receive the the HttpRequest in your controller and then convert the xml data to VendorData using your own java code.

Jackson JSON pretty print using annotations

I'm using #JSONCreator and #JsonCreator to convert a response bean to JSON in Lagom Framework. But, the JSON is not formatted. How can I pretty print the JSON using the annotations (not ObjectMapper)? Here's my sample response bean:
#Immutable
#JsonDeserialize
public class foo {
private final List<Result> _result;
private final MetadataBean _meta;
#JsonCreator
public foo (List<Result> _result, MetadataBean _meta) {
this._result= _result;
this._meta = _meta;
}
}
It seems that pretty printing is controlled by the ObjectMapper and cannot be influenced by annotations. The Lagom documentation for negotiated serializers has this example:
public class JsonTextSerializer implements MessageSerializer.NegotiatedSerializer<String, ByteString> {
private final ObjectMapper mapper = new ObjectMapper();
#Override
public MessageProtocol protocol() {
return new MessageProtocol(Optional.of("application/json"), Optional.empty(), Optional.empty());
}
#Override
public ByteString serialize(String s) throws SerializationException {
try {
return ByteString.fromArray(mapper.writeValueAsBytes(s));
} catch (JsonProcessingException e) {
throw new SerializationException(e);
}
}
}
Pretty printing can then be enabled on the mapper (probably in a constructor):
mapper.enable(SerializationFeature.INDENT_OUTPUT);

SerializationFeature.WRAP_ROOT_VALUE as annotation in jackson json

Is there a way to have the configuration of SerializationFeature.WRAP_ROOT_VALUE as an annotation on the root element instead using ObjectMapper?
For example I have:
#JsonRootName(value = "user")
public class UserWithRoot {
public int id;
public String name;
}
Using ObjectMapper:
#Test
public void whenSerializingUsingJsonRootName_thenCorrect()
throws JsonProcessingException {
UserWithRoot user = new User(1, "John");
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
String result = mapper.writeValueAsString(user);
assertThat(result, containsString("John"));
assertThat(result, containsString("user"));
}
Result:
{
"user":{
"id":1,
"name":"John"
}
}
Is there a way to have this SerializationFeature as an annotation and not as an configuration on the objectMapper?
Using dependency:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.2</version>
</dependency>
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test2 {
public static void main(String[] args) throws JsonProcessingException {
UserWithRoot user = new UserWithRoot(1, "John");
ObjectMapper objectMapper = new ObjectMapper();
String userJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);
System.out.println(userJson);
}
#JsonTypeName(value = "user")
#JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
private static class UserWithRoot {
public int id;
public String name;
}
}
#JsonTypeName and #JsonTypeInfo together make it possible.
Result:
{
"user" : {
"id" : 1,
"name" : "John"
}
}
I think this has been requested as:
https://github.com/FasterXML/jackson-databind/issues/1022
so if anyone wants a challenge & a chance to make many users happy (it is something that'd be nice to have for sure), it's up for grabs :)
Aside from that one small thing worth noting is that you can use ObjectWriter to enable/disable SerializationFeatures.
String json = objectMapper.writer()
.with(SerializationFeature.WRAP_ROOT_VALUE)
.writeValueAsString(value);
in case you need to sometimes use this, other times not (ObjectMapper settings should not be changed after initial construction and configuration).
You can use the it in the below way, so this property will be applied to throughout the objectMapper usage.
static {
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
}

Categories

Resources