Jackson deserialize GeoJson Point in Spring Boot

Viewed 11286

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.

5 Answers

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.

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