question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

feign client page object error

See original GitHub issue
@FeignClient("notice-service-provider")
public interface NoticeService {

  @RequestMapping(value = NoticeConstants.NOTICES_PATH, method = RequestMethod.GET)
  ResponseEntity<Page<NoticeEntity>> findAll();

}

if service return Page,get error:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.data.domain.Page, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: java.io.PushbackInputStream@67635da8; line: 1, column: 1]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:892) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:139) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3736) ~[jackson-databind-2.6.4.jar:2.6.4]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2810) ~[jackson-databind-2.6.4.jar:2.6.4]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:221) ~[spring-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
    ... 30 common frames omitted

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:8 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
jamesbassettcommented, Jun 22, 2016

Hi all, thanks for the feign integration - it’s really neat!

We worked around this issue by:

  • overriding the default SpringMvcContract in the client config to deserialize Page as Spring’s PageImpl
  • writing a custom Jackson module to deserialize PageImpl

It would be a bit simpler to use your own Page implementation, but we wanted to use Spring’s dammit!

Code is in kotlin, but you get the idea 😃

Customized contract

Make Page -> PageImpl

import feign.MethodMetadata
import org.apache.commons.lang3.reflect.TypeUtils
import org.springframework.cloud.netflix.feign.support.SpringMvcContract
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
class APIContract : SpringMvcContract() {

    override fun parseAndValidateMetadata(targetType: Class<*>?, method: Method?): MethodMetadata? {
        return super.parseAndValidateMetadata(targetType, method).apply {
            updatePageReturnType(this)
        }
    }

    private fun updatePageReturnType(metadata: MethodMetadata) {
        val returnType = metadata.returnType()

        if (returnType is ParameterizedType && returnType.rawType == Page::class.java) {
            metadata.returnType(TypeUtils.parameterizeWithOwner(returnType.ownerType, PageImpl::class.java,
                    *returnType.actualTypeArguments))
        }
    }
}

Jackson module

Could probably have also achieved this via a Deserializer…

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.CreatorProperty
import com.fasterxml.jackson.databind.deser.SettableBeanProperty
import com.fasterxml.jackson.databind.deser.ValueInstantiator
import com.fasterxml.jackson.databind.deser.ValueInstantiators
import com.fasterxml.jackson.databind.deser.std.StdValueInstantiator
import com.fasterxml.jackson.databind.module.SimpleDeserializers
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.ArrayNode
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import java.io.IOException
import java.util.*

/**
 * Jackson module to configure paging related serialization/deserialization.
 */
class PagingModule : SimpleModule("PagingModule") {
    override fun setupModule(context: SetupContext) {
        super.setupModule(context)
        context.addDeserializers(SimpleDeserializers().apply {
            addDeserializer(Sort::class.java, SortDeserializer())
        })
        context.addValueInstantiators(PageImplValueInstantiators())
    }
}

/**
 * Registers PageImplValueInstantiator for instantiating PageImpl instances.
 */
internal class PageImplValueInstantiators : ValueInstantiators {

    override fun findValueInstantiator(config: DeserializationConfig, beanDesc: BeanDescription, defaultInstantiator: ValueInstantiator): ValueInstantiator {
        if (!beanDesc.beanClass.isAssignableFrom(PageImpl::class.java)) {
            return defaultInstantiator
        }
        return beanDesc.toPageImplValueInstantiator(defaultInstantiator)
    }

    fun BeanDescription.toPageImplValueInstantiator(defaultInstantiator: ValueInstantiator): ValueInstantiator {
        if (type.containedTypeCount() == 1) {
            val contentType = type.containedType(0).rawClass
            return PageImplValueInstantiator(defaultInstantiator as StdValueInstantiator, contentType)
        } else {
            throw IllegalStateException("No generic type specified for PageImpl!")
        }

    }
}

/**
 * Jackson value instantiator for PageImpl instances.
 */
internal class PageImplValueInstantiator(
        private val valueInstantiator: StdValueInstantiator,
        private val contentType: Class<*>)
: StdValueInstantiator(valueInstantiator) {

    override fun canCreateFromObjectWith(): Boolean = true

    override fun getFromObjectArguments(config: DeserializationConfig): Array<out SettableBeanProperty> {
        val intType = config.constructType(Int::class.java)
        val longType = config.constructType(Long::class.java)
        // MutableList is used as the PageImpl's content is mutable from Kotlin's perspective
        val contentElementType = config.typeFactory.constructCollectionType(MutableList::class.java, contentType)
        val sortType = config.constructType(Sort::class.java)
        return arrayOf(
                creatorProperty("content", contentElementType, 0, PropertyMetadata.STD_REQUIRED),
                creatorProperty("number", intType, 1, PropertyMetadata.STD_REQUIRED),
                creatorProperty("size", intType, 2, PropertyMetadata.STD_REQUIRED),
                creatorProperty("sort", sortType, 3, PropertyMetadata.STD_OPTIONAL),
                creatorProperty("totalElements", longType, 4, PropertyMetadata.STD_REQUIRED)
        )
    }

    override fun createFromObjectWith(ctxt: DeserializationContext, args: Array<out Any>): Any {
        val content = args[0] as MutableList<*>?
        val number = args[1] as Int
        val size = args[2] as Int
        val sort = args[3] as? Sort
        val totalElements = args[4] as Long
        return PageImpl(content, PageRequest(number, size, sort), totalElements)
    }

    private fun creatorProperty(name: String, type: JavaType, index: Int, metadata: PropertyMetadata): CreatorProperty {
        return CreatorProperty(PropertyName.construct(name), type, null,
                null, null, null, index, null, metadata)
    }
}

class SortDeserializer : JsonDeserializer<Sort>() {

    @Throws(IOException::class)
    override fun deserialize(jp: JsonParser, ctxt: DeserializationContext): Sort {
        val orders = ArrayList<Sort.Order>()

        val oc = jp.codec
        val arrayNode = oc.readTree<ArrayNode>(jp)

        for (i in 0..arrayNode.size() - 1) {
            val node = arrayNode.get(i)

            val property = node.get("property").textValue()
            val directionValue = node.get("direction").textValue()
            val direction = Sort.Direction.fromString(directionValue)

            val order = Sort.Order(direction, property)
            orders.add(order)
        }

        return Sort(orders)
    }
}
0reactions
YooLincommented, Jul 19, 2019

@jamesbassett

It works, thanks!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Jackson with Feign can't deserialized Spring's org ...
The problem: A feign client, making an API call to a Spring boot Rest API that returns a Page<T> can't deserialize the sort...
Read more >
Feign Client Exception Handling - Baeldung
We'll demonstrate how to handle exceptions in Feign in a Spring ... When an error occurs, the Feign client suppresses the original message....
Read more >
Feign Error Handling with ErrorDecoder - Apps Developer Blog
In this tutorial, I will share with you how you can use Feign ErrorDecoder to handle errors that occur when using Feign client...
Read more >
Spring Cloud OpenFeign
Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it....
Read more >
Decoder - OpenFeign/feign - Gitter
return HystrixFeign.builder() .client(defaultClient) .target(PongClient.class, ... Pageable object properties are bound with GET page=<page_num>, ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found