Search code examples
javaspringkotlinfeign

Post json of unknown class with Feign client


My service has to redirect a json data to another service. In all examples I saw on feign client, sender knows the data type of the sent object, and so it looks like this:

@RequestMapping("some/path", consumes = [MediaType.APPLICATION_JSON_VALUE])
interface UploadClient {

    @PostMapping("upload")
    fun upload(book: Book): Long
}

My problem is I have no Book class, I just have to forward json. I've tried to do it with String, like this:

Client interface:

@RequestMapping("some/path")
interface UploadClient {

    @PostMapping("/upload", consumes = arrayOf(MediaType.APPLICATION_JSON_VALUE))
    fun import(@RequestBody strangeData: String): Long

}

Feign Factory:

class DefaultFeignClientFactory() : FeignClientFactory {

override fun <T> getClient(target: Class<T>, url: String): T {
    return registry.getOrPut(target) {
        Feign.builder()
            .target(target, url)
    } as T
}

}

Client is instantiated using OSGi:

  <bean id="uploadClient" factory-ref="feignClientFactory" factory-method="getClient">
    <argument value="com.blah.blahblah.UploadClient"/>
  </bean>

And the same interface on the receiver side is:

 @RequestMapping("some/path")
 interface Loader {

     @PostMapping("/upload")
     fun import(@RequestBody myBooks: Collection<Book>): Long

 }

If I throw json in the receiver directly (curl/postman/etc), it deserializes it and works fine. Passing the string to feign client works, but unfortunately, receiver can't deserialize it that way. So I tried to change it to byte[] (ByteArray in Kotlin):

    @PostMapping("/upload", consumes = arrayOf(MediaType.APPLICATION_JSON_VALUE))
    fun import(@RequestBody strangeData: ByteArray): Long

and it falls with org.springframework.web.HttpMediaTypeNotSupportedException message: Unsupported Media Type Same luck with MediaType.APPLICATION_OCTET_STREAM_VALUE and MediaType.ALL_VALUE (actually ALL_VALUE fell with some other error) - data is not sent to the receiver service in any form.

What Java data type and what MediaType do I have to choose in those circumstances? Thanks in advance.


Solution

  • One way to state that your method is accepting generic JSON is by using Jackson's JsonNode as your parameter type.

    @RequestMapping(value = ["some/path"], consumes = [MediaType.APPLICATION_JSON_VALUE])
    interface UploadClient {
    
        @PostMapping("upload")
        fun upload(@RequestBody book: JsonNode): Long
    }
    

    This way, you are able to post JSON Objects, JSON Arrays, Strings, Numbers or Booleans with arbitrary nesting. Here's an example:

    val book: ObjectNode = JsonNodeFactory.instance.objectNode()
            .put("authors", JsonNodeFactory.instance.arrayNode()
                    .add("John Doe")
                    .add("Jane Doe"))
            .put("title", "First Book about the Does")
            .put("year", 2018)
    
    val id = uploadClient.upload(book)
    

    This is equivalent to the following request

    POST /some/path HTTP/1.1
    Host: localhost:8080
    Content-Type: application/json
    
    {
      "authors": ["John Doe", "Jane Doe"],
      "title": "First Book about the Does",
      "year": 2018
    }
    

    Using JsonNode will also work on the server side.