Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 958
Tom Avatar asked Aug 16 '17 12:08

Tom


2 Answers

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>
like image 89
Kruschenstein Avatar answered Sep 19 '22 21:09

Kruschenstein


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.

like image 26
Ghostli Avatar answered Sep 16 '22 21:09

Ghostli