Skip to content

AbstractJackson2MessageConverter does not work with other ObjectMappers from jackson-dataformats-binary #3038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ajafff opened this issue Apr 8, 2025 · 3 comments

Comments

@ajafff
Copy link

ajafff commented Apr 8, 2025

In what version(s) of Spring AMQP are you seeing this issue?

For example:

3.2.4

Describe the bug

Example uses CBORMapper from jackson-dataformat-cbor

To Reproduce

  • Use RabbitTemplate#convertAndSend() to serialize an object and send it to a queue
  • Use @RabbitListener to read message from queue and deserialize to object

Expected behavior

Serializing and sending works.
Deserializing does not work because convertBytesToObject converts the payload to UTF-8 string before handing it to the ObjectMapper, causing Can not create parser for non-byte-based source.
I could get deserialization to work if I add a ;charset=ascii to the MimeType, as that causes the raw payload to be handed to the ObjectMapper. Unfortunately this hack causes serialization to fail, because it now calls ObjectMapper#writeValueAsString which is not supported for CBOR.

This is basically asking for it to work out of the box, or being able to override convertBytesToObject (currently private) and a new method convertObjectToBytes (so that I don't have to override and copy the contents of createMessage)

Sample

public class CborMessageConverter extends AbstractJackson2MessageConverter {
    public CborMessageConverter() {
        super(new CBORMapper(), MimeTypeUtils.parseMimeType("application/cbor"));
    }
}
@artembilan
Copy link
Member

The logic there on serialization like this:

			if (this.charsetIsUtf8 && this.supportedCTCharset == null) {
				bytes = this.objectMapper.writeValueAsBytes(objectToConvert);
			}
			else {
				String jsonString = this.objectMapper
						.writeValueAsString(objectToConvert);
				String encoding = this.supportedCTCharset != null ? this.supportedCTCharset : getDefaultCharset();
				bytes = jsonString.getBytes(encoding);
			}

So, if it is UTF-8 (technically no MimeType provided for the Jackson2JsonMessageConverter), then we write content as byte[].

On the deserialization we have a different logic:

	private Object convertBytesToObject(byte[] body, String encoding, JavaType targetJavaType) throws IOException {
		if (this.supportedCTCharset != null) { // Jackson will determine encoding
			return this.objectMapper.readValue(body, targetJavaType);
		}
		String contentAsString = new String(body, encoding);
		return this.objectMapper.readValue(contentAsString, targetJavaType);
	}

I guess we can improve that with the this.charsetIsUtf8 flag as well.
But at the same time I don't deny making convertBytesToObject() as protected and have that CborMessageConverter.

Let us know if you are willing to contribute the fix: https://github.com/spring-projects/spring-amqp/blob/main/CONTRIBUTING.adoc !

@ajafff
Copy link
Author

ajafff commented Apr 9, 2025

thank you for your response @artembilan

I can try to contribute a fix. However, it's not clear to me, what the preferred solution would be.

I'm wondering if we want to use this.objectMapper.getFactory().canHandleBinaryNatively() to determine if we always work with byte arrays instead of doing string conversion/encoding stuff.

@artembilan
Copy link
Member

I don't think so.
See its JavaDocs:

    /**
     * Introspection method that higher-level functionality may call
     * to see whether underlying data format can read and write binary
     * data natively; that is, embeded it as-is without using encodings
     * such as Base64.
     *<p>
     * Default implementation returns <code>false</code> as JSON does not
     * support native access: all binary content must use Base64 encoding.
     * Most binary formats (like Smile and Avro) support native binary content.
     *
     * @return Whether format supported by this factory
     *    supports native binary content
     *
     * @since 2.3
     */
    @Override
    public boolean canHandleBinaryNatively() {

So, this one is for case where a property of our data object is a byte[] itself.
Doesn't look like this has anything to do with (de)serialization itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants