Spring Data REST - JsonGenerationException: Can not write a number, expecting field name

时间:2016-07-11 20:17:18

标签: spring-boot spring-data spring-data-rest jpa-2.1 jackson2

I'm running into an issue with a Spring Data REST application using the Spring Boot starter. I have a number of entities defined with various relationships. I'm able to retrieve them by key (generally), but I'm having problems with some collections of the same objects. I'm wondering if this is a JsonIdentityInfo issue or otherwise "breaking" the unique identifier stuff to prevent circular JSON generation.

There are ~500 entries in the Company table, and the page size is defaulting to 20. Going to the default endpoint (/company), it returns the error below. I can pull up individual Companies without issue (/company/1), including the same Company that it is trying to generate when the JSON exception is created.

When I step into the exception stack, I see that it is trying to generate JSON for the supportEmailAddress field. This is an row that may be referenced by multiple Company rows. Companies also have Contacts that have EmailAddresses in this table as well, but these are generally not shared between Companies or Contacts.

NOTE: I've looked at the SO question with a similar stack trace, but that question seemed revolve around a custom Serializer. I am not using a custom serializer.

A few things I've tried:

  • JsonIdentityInfo with and without scope attribute
  • JsonManagedReference and JsonBackReference
  • @Access(AccessType.PROPERTY) on @Id field

Library versions:

ext['hibernate.version'] = '5.1.0.Final'
ext['hibernateVersion'] = '5.1.0.Final'
ext['springVersion'] = '2.5.1.RELEASE'
ext['springBootVersion'] = '1.3.5.RELEASE'
ext['springDataCommonsVersion'] = '1.12.1.RELEASE'
ext['springDataJpaVersion'] = '1.10.1.RELEASE'
ext['springIntegrationVersion'] = '4.2.6.RELEASE'
ext['querydslVersion'] = '4.1.0'
ext['jacksonVersion'] = '2.8.0'
ext['jacksonJsr310Version'] = '2.8.0'

I've tried debugging through the serialization and the root issue is that the serializer gets confused when processing the company.supportEmailAddress.key. It is trying to output the key value but the serializer expects the key name to be next. The error occurs when the second reference to the same supportEmailAddress id is made.

Updated to Jackson 2.8.0. No change.

I added a simplified, handwritten example JSON at the end to show the structure I'm expecting. As you can see, the two Companies refer to the same supportEmailAddress object (same id). If I change to a different id, it renders correctly. I believe the second reference would actually just output the id instead of the remainder of the object, since it was already serialized once. It's my understanding this is a standard feature, and I've not done anything related to changing the Jackson default features.

Simplified Entities (accessors omitted):

Company:

@Entity
@Table(name = "T_Company")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "key", scope = Company.class)
public class Company extends AbstractCustomEntity<Long> {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "COMPANY_ID")
private Long key;

@Size(max = 50)
@Column(name = "NAME", nullable = false, length = 50, unique = true)
private String name;

@OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "company")
private Set<Alias> aliases;

@ManyToMany(mappedBy = "company")
private Set<Owner> owner;

@OneToMany(mappedBy = "agency", cascade = CascadeType.ALL)
private Set<Contact> contacts;

@ManyToOne
@JoinColumn(name = "SUPPORT_EMAIL_ADDRESS_ID")
private EmailAddress supportEmailAddress;

Owner:

@Entity
@Table(name = "T_OWNER")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "key", scope = Owner.class)
public class Owner extends AbstractCustomEntity<Long> {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "VENDOR_ID", nullable = false, updatable = false)
private Long key;

@NotNull
@Size( max = 50 )
@Column(name = "NAME",nullable = false, length = 50, unique = true)
private String name;

@ManyToMany( cascade = CascadeType.ALL)
@JoinTable(name = "T_OWNER_COMPANY"
, joinColumns = {@JoinColumn(name = "OWNER_ID")}
        , inverseJoinColumns = {@JoinColumn(name = "COMPANY_ID")}
)
private Set<Company> companies;

@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
private Set<Contact> contacts;

EmailAddress:

@Entity
@Table(name = "T_EMAIL_ADDR")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "key", scope = EmailAddress.class)
public class EmailAddress extends AbstractCustomEntity<Long> {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "EMAIL_ADDRESS_ID")
private Long key;

@Column(name = "EMAIL_ADDRESS_TYPE_NME")
@Enumerated(EnumType.STRING)
private EmailAddrType emailAddrType;

@Size(max = 200)
@Email
@Column(name = "EMAIL_ADDR", length = 200, nullable = false, updatable = false)
private String emailAddress;

Contact:

@Entity
@Table(name = "T_CONTACT")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "CATG", length = 6)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "key", scope = Contact.class)
public abstract class Contact extends AbstractCustomEntity<Long> {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "CONTACT_ID")
private Long key;

@Column(name = "CONTACT_NME", length = 100)
@Size(max = 100)
private String name;

@OneToOne
@JoinColumn(name = "EMAIL_ADDRESS_ID")
private EmailAddress emailAddress;

Stack trace:

org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Can not write a number, expecting field name; nested exception is com.fasterxml.jackson.core.JsonGenerationException: Can not write a number, expecting field name
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:276)
at org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:100)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:222)
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:183)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
at org.springframework.data.rest.webmvc.ResourceProcessorHandlerMethodReturnValueHandler.handleReturnValue(ResourceProcessorHandlerMethodReturnValueHandler.java:113)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:832)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:743)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.AbstractRequestLoggingFilter.doFilterInternal(AbstractRequestLoggingFilter.java:220)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:87)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:522)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1095)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1502)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1458)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: com.fasterxml.jackson.core.JsonGenerationException: Can not write a number, expecting field name
at com.fasterxml.jackson.core.JsonGenerator._reportError(JsonGenerator.java:1676)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator._verifyValueWrite(UTF8JsonGenerator.java:925)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator.writeNumber(UTF8JsonGenerator.java:787)
at com.fasterxml.jackson.databind.ser.std.NumberSerializers$LongSerializer.serialize(NumberSerializers.java:188)
at com.fasterxml.jackson.databind.ser.impl.WritableObjectId.writeAsId(WritableObjectId.java:35)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase._serializeWithObjectId(BeanSerializerBase.java:584)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer.serialize(UnwrappingBeanSerializer.java:114)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter.serializeAsField(UnwrappingBeanPropertyWriter.java:127)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:678)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157)
at com.fasterxml.jackson.databind.SerializerProvider.defaultSerializeValue(SerializerProvider.java:985)
at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer.serialize(PersistentEntityJackson2Module.java:193)
at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer.serialize(PersistentEntityJackson2Module.java:140)
at com.fasterxml.jackson.databind.SerializerProvider.defaultSerializeValue(SerializerProvider.java:985)
at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$NestedEntitySerializer.serialize(PersistentEntityJackson2Module.java:356)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:672)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:678)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase._serializeWithObjectId(BeanSerializerBase.java:600)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer.serialize(UnwrappingBeanSerializer.java:114)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter.serializeAsField(UnwrappingBeanPropertyWriter.java:127)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:678)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157)
at com.fasterxml.jackson.databind.SerializerProvider.defaultSerializeValue(SerializerProvider.java:985)
at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer.serialize(PersistentEntityJackson2Module.java:193)
at org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer.serialize(PersistentEntityJackson2Module.java:140)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:616)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:519)
at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:31)
at org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.serialize(Jackson2HalModule.java:340)
at org.springframework.hateoas.hal.Jackson2HalModule$HalResourcesSerializer.serialize(Jackson2HalModule.java:302)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:672)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:678)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:157)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:130)
at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1428)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:930)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:269)
... 57 more

Example JSON:

{
  "_embedded" : {
"companies" : [ {
  "key" : 1,
  "name" : "company1",
  "supportEmailAddress" : {
    "key" : 1,
    "emailAddrType" : "support",
    "emailAddress" : "email@support.com"
  }
  "aliases" : [ ],
  "contacts" : [ ],
  "owner" : {
    "key" : 1,
    "name" : "owner 1",
    "contacts" : [ ],
    "_links" : {
      "company" : {
        "href" : "http://localhost:8080/company/1"
      }
    }
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/company/1"
    },
    "company" : {
      "href" : "http://localhost:8080/company/1"
    },
    "aliases" : {
      "href" : "http://localhost:8080/company/1/aliases"
    },
    "contacts" : {
      "href" : "http://localhost:8080/company/1/contacts"
    }
  }
}, {
  "key" : 2,
  "name" : "company2",
  "supportEmailAddress" : {
    "key" : 1,
    "emailAddrType" : "support",
    "emailAddress" : "email@support.com"
  }
  "aliases" : [ ],
  "contacts" : [ ],
  "owner" : {
    "key" : 2,
    "name" : "owner 2",
    "contacts" : [ ],
    "_links" : {
      "company" : {
        "href" : "http://localhost:8080/company/2"
      }
    }
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/company/2"
    },
    "company" : {
      "href" : "http://localhost:8080/company/2"
    },
    "aliases" : {
      "href" : "http://localhost:8080/company/2/aliases"
    },
    "contacts" : {
      "href" : "http://localhost:8080/company/2/contacts"
    }
  }
} ]
  },
  "_links" : {
"self" : {
  "href" : "http://localhost:8080/company"
},
"profile" : {
  "href" : "http://localhost:8080/profile/company"
},
"search" : {
  "href" : "http://localhost:8080/company/search"
}
  },
  "page" : {
"size" : 20,
"totalElements" : 2,
"totalPages" : 1,
"number" : 0
  }
}

1 个答案:

答案 0 :(得分:0)

我在嵌套实体上指定了EAGER提取时遇到了同样的错误(例如@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL) private Set<Contact> contacts;实体上的Owner

只要嵌套集合中有多个项目,序列化就会停止工作。通过在嵌套集合上指定FetchType.LAZY,我能够解决这个问题,我想这样串行器不会与递归混淆?

我很想听听更熟悉杰克逊的人的正确解释