10

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.

Tom
  • 3,807
  • 4
  • 33
  • 58
  • Do you want `Point location` not to serialize ? or something else ? If you have @JsonIgnore then object is not serialized. – sunkuet02 Aug 16 '17 at 12:57
  • No, i want `Point location` to be serialized. The `@JsonIgnore` is just a temporary workaround with the custom getter, so Jackson won't die while serializing the `Point`. – Tom Aug 16 '17 at 13:09
  • have you found a solution ? – Karl.S Dec 07 '17 at 02:59

5 Answers5

7

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.

Ghostli
  • 383
  • 1
  • 3
  • 11
  • How do you register jts module with jackson? – Mandroid Jun 13 '20 at 14:57
  • I basically have to make things work with spring boot. My service returns a list of entities, with each entity having a locationtech.jts.geom.Geometry field. But this geometry field is causing a recursive response on client side. – Mandroid Jun 13 '20 at 15:06
  • @Mandroid Have you found the solution? can you share please? – Shehan Simen Sep 07 '21 at 08:47
  • @ShehanSimen do you have a problem with the recursion or making spring boot work with JTS? – Ghostli Sep 08 '21 at 10:56
  • @Ghostli yes, really annoying. Do you have a solution for that? I created my own POJO classes to serialize at the end as I couldn't find a solution – Shehan Simen Sep 10 '21 at 13:54
  • This solution worked for me (in Spring Boot), incidentally I did not need to add the JTS Module, but I added the @De/Serializer annotation to the model I was converting from. – Leroy Mar 16 '22 at 10:27
  • Anyone able to look at similar issue pls https://stackoverflow.com/questions/77026240/json-deserialize-geolattegeom-point-in-spring-boot – Al Grant Sep 01 '23 at 22:28
5

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>
Kruschenstein
  • 283
  • 4
  • 12
  • Just had the same problem myself, two minor suggestions ` com.bedatadriven jackson-datatype-jts 2.4 ` Also to use with Spring boot you need to include `import com.bedatadriven.jackson.datatype.jts.JtsModule;` – Dave Dec 24 '17 at 07:50
  • Thanks @Kruschenstein! Adding JtsModule bean solves the problem. – Ranic Aug 07 '18 at 07:38
  • 1
    @Kruschenstein - I am using Springboot. Where do I put the Bean JtsModule? – Al Grant Dec 10 '19 at 23:23
  • @AlGrant you can put the bean in any `@Configuration` annoted class that is scanned by Spring Framework. Like any `@Bean` managed by spring! – Kruschenstein Dec 13 '19 at 21:33
  • These steps cause spring boot to reply with error 500. – Mandroid Jun 13 '20 at 14:59
  • 1
    Try changing to your dependency to: ` org.n52.jackson jackson-datatype-jts 1.2.9 ` This fixed my issues with serialization of Geometries. – Ethan Davis Jan 31 '22 at 19:26
0

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

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

Saravanan
  • 566
  • 5
  • 13
0

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;
        }
    }
Vaibs
  • 1,546
  • 9
  • 31