Jackson 在 Spring Boot 中反序列化 GeoJson Point

Posted

技术标签:

【中文标题】Jackson 在 Spring Boot 中反序列化 GeoJson Point【英文标题】:Jackson deserialize GeoJson Point in Spring Boot 【发布时间】:2018-01-24 14:51:11 【问题描述】:

我有一个@Entity 模型,它的属性类型为com.vividsolutions.jts.geom.Point。当我尝试在 @RestController 中渲染这个模型时,我得到一个递归异常。

(***Error); nested exception is 
com.fasterxml.jackson.databind.JsonMappingException: Infinite 
recursion (***Error) (through reference chain: 
com.vividsolutions.jts.geom.Point[\"envelope\"]-
>com.vividsolutions.jts.geom.Point[\"envelope\"]....

实体看起来像这样(为了简洁而缩短):

@Entity
@Data
public class MyEntity
    // ...
    @Column(columnDefinition = "geometry")
    private Point location;
    // ...

经过一番研究,我发现这是因为 Jackson 默认无法反序列化 GeoJson。添加这个库应该可以解决问题:https://github.com/bedatadriven/jackson-datatype-jts。

我现在不确定如何在 Spring Boot 的对象映射器中包含这个模块。根据 boot 中的文档,我尝试通过以下两种方式将其添加到 @Configuration

@Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() 
    Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
    builder.modulesToInstall(new JtsModule());
    return builder;

@Bean
public JtsModule jtsModule()
    return new JtsModule();

两者都没有删除异常。抱歉,如果这是重复的,但我能找到的只是自定义 ObjectMapper,据我了解,这不是“春季启动方式”。

作为一种解决方法,我是 @JsonIgnoreing Point 并为不存在的协调对象提供自定义 getter 和 setter,...但这不是我想要保留它的方式。

【问题讨论】:

你想让Point location 不序列化吗?或者是其他东西 ?如果您有 @JsonIgnore 则对象未序列化。 不,我希望Point location 被序列化。 @JsonIgnore 只是自定义 getter 的临时解决方法,因此 Jackson 在序列化 Point 时不会死。 你找到解决办法了吗? 【参考方案1】:

请尝试进行如下更改,然后重试..

  <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

如下更改模型。

@Entity
@Data
public class MyEntity
    // ...
    @Column(columnDefinition = "geometry")
    @JsonDeserialize(as = Point.class)
    private Point location;
    // ...

如果上述配置不适用于您的 JacksonSerializer 类,请尝试以下一次。

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;
        
    

【讨论】:

【参考方案2】:

当我在 Spring Boot 中处理 Spring Boot 空间数据类型时,com.vividsolutions.jts.geom.Point 给我带来了很多问题。目前,我正在使用 Point of type

org.locationtech.jts.geom.Point

这就像一个魅力

【讨论】:

【参考方案3】:

上述使用 JTSModule 的解决方法会导致我出现内部 SpringBoot 错误。我能够通过确保我的实体的 getter 方法返回字符串类型来解决这个问题。

@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(); 

【讨论】:

【参考方案4】:

截至 2020 年,大多数 JTS 库都已过时,不再工作。我在 Maven Central 上发现了一个最近更新的分支,它与 jackson-core:2.10.0jts-core:1.16.1 完美配合:

implementation 'org.n52.jackson:jackson-datatype-jts:1.2.4'

示例用法:

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

您不需要在类字段上使用任何注释或注册新的 Spring Bean,只需向 Jackson 注册 JTS 模块即可。

【讨论】:

如何向jackson注册jts模块? 我基本上必须使用 spring boot 来解决问题。我的服务返回一个实体列表,每个实体都有一个 locationtech.jts.geom.Geometry 字段。但是这个几何字段正在客户端引起递归响应。 @Mandroid 你找到解决方案了吗?可以分享一下吗? @ShehanSimen 您是否对递归或使 Spring Boot 与 JTS 一起工作有问题? @Ghostli 是的,真的很烦人。你有解决方案吗?我创建了自己的 POJO 类以在最后进行序列化,因为我找不到解决方案【参考方案5】:

也许您应该使用@JsonSerialize@JsonDeserialize 标记您的几何属性。像这样:

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;
    // ...

如果您使用的是 Spring-Boot,您只需要:

import com.bedatadriven.jackson.datatype.jts.JtsModule;
// ...
@Bean
public JtsModule jtsModule()

    return new JtsModule();

正如 Dave 所说,您需要将此依赖项添加到您的 pom.xml:

<dependency>
    <groupId>com.bedatadriven</groupId>
    <artifactId>jackson-datatype-jts</artifactId>
    <version>2.4</version>
</dependency>

【讨论】:

我自己也遇到了同样的问题,有两个小建议&lt;dependency&gt; &lt;groupId&gt;com.bedatadriven&lt;/groupId&gt; &lt;artifactId&gt;jackson-datatype-jts&lt;/artifactId&gt; &lt;version&gt;2.4&lt;/version&gt; &lt;/dependency&gt;另外要与Spring boot一起使用,您需要包含import com.bedatadriven.jackson.datatype.jts.JtsModule; 谢谢@Kruschenstein!添加 JtsModule bean 即可解决问题。 @Kruschenstein - 我正在使用 Springboot。我应该把 Bean JtsModule 放在哪里? @AlGrant 您可以将 bean 放在 Spring Framework 扫描的任何 @Configuration 注释类中。像 spring 管理的任何@Bean 一样! 这些步骤会导致 spring boot 回复错误 500。

以上是关于Jackson 在 Spring Boot 中反序列化 GeoJson Point的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring Boot + Spring Data Rest 中反序列化时忽略带有 @JsonProperty 的字段

无法在 Spring Boot 中反序列化嵌套对象“Role”

Spring Boot REST 服务:JSON 反序列化不起作用

无法从 START_OBJECT 令牌中反序列化 `java.lang.Long` 的实例;在 Spring Boot 帖子上

如何在 Spring Boot 1.4 中自定义 Jackson

Spring boot与Jackson ObjectMapper