Jackson deserialize GeoJson Point in Spring Boot - java

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;
}
}

Related

Json Deserialization and save to JPA on boot

Good morning,
I consume API in JSON format, data on the latest exchange rates.
I want this data to be downloaded to me at the beginning of the application and saved in the database. I use spring JPA.
The problem is I do not know how I should write it down.
I have a class responsible for the connection which returns the output in the form of a String.
Another creates de-serialization.
I also have two classes of model that I can use to download data.
I do not want to create a separate class in which the program will pull out each value individually. I was thinking about the map but I do not know how to do it.
Some code:
Model 1
#Data
#AllArgsConstructor
#Entity
public class CurrencyData {
#Id
#GeneratedValue( strategy = GenerationType.AUTO )
private Long id;
#SerializedName("rates")
#Expose
#Embedded
private Rates rates;
#SerializedName("base")
#Expose
#Embedded
private String base;
#SerializedName("date")
#Expose
#Embedded
private String date;
}
Model 2
#Data
#AllArgsConstructor
#Embeddable
public class Rates {
protected Rates(){}
#SerializedName("CAD")
#Expose
private Double cAD;
#SerializedName("HKD")
}
ConnectService with string api output
private static final String REQUEST_CURRENCY = "https://api.exchangeratesapi.io/latest?base=USD";
public String connect() {
String output = null;
try {
System.out.println("URL String : " + REQUEST_CURRENCY);
URL url = new URL(REQUEST_CURRENCY);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
if (conn.getResponseCode() != 200) {
throw new TODO("TODO : ", e.getMessage());
} else {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
output = response.toString();
}
} catch (Exception e) {
throw new OutputFromApiException("ConnectService CurrencyData-API: output is : ", e.getMessage());
}
return output;
}
GsonConvert- Deserialization
public CurrencyData gsonCurrency(String answer) {
Gson g = new Gson();
CurrencyData currencyData = null;
try {
currencyData = g.fromJson(answer, CurrencyData.class);
} catch (Exception e) {
throw new OutputFromApiException("HistoricalFlight API output is empty ", e.toString());
}
return currencyData;
}
Repository
#Repository
public interface CurrencyRepository extends JpaRepository<CurrencyData, Long> {
}
... And probably I have to write something here..
#Bean
CommandLineRunner runner(CurrencyRepository currencyRepository) {
return args -> {
currencyRepository.save();
};
}
If you are using Spring Boot I think you should define a main class that implements CommandLineRunner instead of defining it as a #Bean. It should be something like:
#SpringBootApplication
public class SpringBootConsoleApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringBootConsoleApplication.class, args);
}
#Autowired
CurrencyRepository currencyRepository;
#Autowired
ConnectService connectionService;
#Override
public void run(String... args) {
String output = connectionService.connect();
CurrencyData currencyData = connectionService.gsonCurrency(output);
currencyRepository.save(currencyData);
}
}
Also I assumed that your jpa configuration is correct and your CurrencyRepository works as expected. If you do not have a manually created database structure than you may consider adding to application.properties file as:
spring.jpa.hibernate.ddl-auto=update
This will provide you that JPA creates or updates the proper database structures on every boot by using your entities configuration.
EDIT:
Sorry I forgot to mention about that you should pass the entity which you want to persist into database. I edited the code as I guess gsonCurrency method is a method inside ConnectionService. Also you can pass a parameter to connectionService.connect() method for base if you want to fetch different data according to different base currencies like this:
CurrencyData currencyDataUSD = connectionService.gsonCurrency(connectionService.connect("USD"));
CurrencyData currencyDataEUR = connectionService.gsonCurrency(connectionService.connect("EUR"));
// and go on if you like
You can use Spring Boot and Rest Template so that you can easily manage the message conversion without having to write the low level HttpConnection. There are two ways to execute a method when an application startup happens in Spring Boot, CommandLineRunner and ApplicationRunner, and here we are using the first as shown below :
#SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String args[]) {
SpringApplication.run(Application.class);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
#Bean
public CommandLineRunner run(RestTemplate restTemplate) throws Exception {
return args -> {
Quote quote = restTemplate.getForObject(
"https://gturnquist-quoters.cfapps.io/api/random", Quote.class);
log.info(quote.toString());
};
}
}
Source: https://spring.io/guides/gs/consuming-rest/

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);
}

Jackson XML custom field deserializer

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.

return just part of the bean with toJson()

I am trying to return {"status": its value}´in the case of routeD!=0 currently I am getting {"status":201,"routes":null} I would get the response in this form {"status":201} without "routes":null at the same time I dont want to lost the response of routeD==0 which is for example {"status":230,"routes":[1,9,3]}
I appeciate any help.
Receiver class:
#Path("/data")
public class Receiver {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public Response storeData(Data data) {
Database db = new Database();
String macD = data.getMac();
int routeD = data.getRoute();
double latD = data.getLatitude();
double longD = data.getLongitude();
double speedD = data.getSpeed();
// Jackson class to wrapper the data in JSON string.
SDBean bean = new SDBean();
if (routeD != 0) {
bean.status = db.insertData(macD, routeD, latD, longD);
return Response.status(bean.status).entity(bean.toJson()).build();
} else {
bean.routes = db.detectRoute(latD, longD);
return Response.status(230).entity(bean.toJson()).build();
}
}
}
SDBean class:
public class SDBean {
public int status;
public ArrayList<Integer> routes;
public SDBean(){
status = 230;
}
public String toJson() {
ObjectMapper mapper = new ObjectMapper();
String json = null;
try {
json = mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
System.out.println(json);
return json;
}
}
Just use #JsonInclude(JsonInclude.Include.NON_NULL)
Annotation used to indicate when value of the annotated property (when used for a field, method or constructor parameter), or all properties of the annotated class, is to be serialized. Without annotation property values are always included, but by using this annotation one can specify simple exclusion rules to reduce amount of properties to write out.
import com.fasterxml.jackson.annotation.JsonInclude;
[...]
#JsonInclude(JsonInclude.Include.NON_NULL)
public class SDBean {

Categories

Resources