Skip to content
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

ObjectMapper customization by autowiring from Spring #535

Open
dominikbrandon opened this issue Jul 19, 2022 · 6 comments
Open

ObjectMapper customization by autowiring from Spring #535

dominikbrandon opened this issue Jul 19, 2022 · 6 comments

Comments

@dominikbrandon
Copy link

Summary
I've got a case where I need to customize the ObjectMapper instance used by JsonUnit, moreover, I don't want to create a new instance of it, but rather use the one provided by Spring context instead.

Solution I'd like
A way to set up JsonUnit's ObjectMapper programmatically instead of via SPI, e.g.

@Configuration
class JsonUnitConfiguration {

    @Bean
    <json unit config class> jsonUnit(ObjectMapper objectMapper) {
        return new <json unit config class>(objectMapper);
    }
}

Do you think something similar would be possible to implement?

Alternative solution
I've come up with a workaround for that, but it's not too good, as there might appear an NPE when JsonUnit tries to access ObjectMapper before Spring autowires it.

import com.fasterxml.jackson.databind.ObjectMapper
import net.javacrumbs.jsonunit.providers.Jackson2ObjectMapperProvider
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component

@Component
class JsonUnitObjectMapperProvider implements Jackson2ObjectMapperProvider {

    private static ObjectMapper objectMapper

    JsonUnitObjectMapperProvider() {
    }

    @Autowired
    JsonUnitObjectMapperProvider(ObjectMapper mapper) {
        objectMapper = mapper
    }

    @Override
    ObjectMapper getObjectMapper(boolean lenient) {
        return objectMapper
    }
}
@lukas-krecan
Copy link
Owner

lukas-krecan commented Jul 19, 2022

Hi, thanks for feedback. This is something that's on my mind for years. All JsonUnit assertion methods are basically static or have a static entry-point. This does not play well with the dynamic nature of Spring. There are two out of this

  1. Expose Spring initialized ObjectMapper on some statis variable and access it from Jackson2ObjectMapperProvider. This is a bit brittle, but should work with usual Spring test setup where you ensure that Spring is initialized before the test.
  2. Expose an entry point to JsonUnit that would allow you to inject the ObjectMapper. This would require me to refactor the library and it will be harder to use. You would have to instantiate and configure an assertion class. I do not have the capacity to do it and I am not sure it would be worth the effort.

@dominikbrandon
Copy link
Author

I see, thanks for the response. What about kind of mixing those two? I mean some static function that would allow you to provide an ObjectMapper instance and then JsonUnit would internally store it in some static field. In my opinion, it would be quite easy to use - it would allow you to call this static function inside the setup method, which would be easy for both: creating and configuring your own instance manually, or using the one autowired by Spring. I believe some people would still prefer to not worry about ObjectMapper at all, so there could be a default one. What do you think about that? The difficulty here would be to provide backward compatibility for providing ObjectMapper using SPI, but I believe this will be possible to achieve in some way.

In case the description is unclear, I mean something like this (it's Groovy+Spock):

abstract class IntegrationSpec extends Specification {

    @Autowired
    ObjectMapper objectMapper

    def setupSpec() {
        JsonUnitNewConfigClass.injectObjectMapper(objectMapper)
    }
}

@lukas-krecan
Copy link
Owner

Yes, that would be possible. I am reluctant to add this as it is quite brittle and I do not want to be solving issues of people using it incorrectly. You can do the same using Jackson2ObjectMapperProvider

@dominikbrandon
Copy link
Author

dominikbrandon commented Jul 20, 2022

Why do you consider this solution brittle? I feel like it's a clear and explicit way of providing ObjectMapper instance and I'm just curious what exactly makes you feel that it's the wrong approach.

And regarding the point that you can do the same using Jackson2ObjectMapperProvider - yes, you're right. I've made it this way and it seems like it's working seamlessly. But doing so made me feel like I'm hacking it - I was particularly worried about a possible NullPointerException in the case of JsonUnit trying to use ObjectMapper before it's been autowired. Yes, it should have been because usually you first run the application which makes the whole Spring context start, then you execute some actions, and finally, you assert using JsonUnit. So assuming that ObjectMapper gets loaded from Jackson2ObjectMapperProvider only when one of the JsonUnit's methods gets called, this would, and does, work. But I didn't know when exactly JsonUnit does that - maybe it's at the start of the execution, or right after that.

Anyway, maybe a cheap and good enough solution would be to put a hint in the docs on how the ObjectMapper may be provided when using Spring, what are your thoughts about that? Maybe this will be helpful for future users of the library.

lukas-krecan added a commit that referenced this issue Jul 4, 2024
@lukas-krecan
Copy link
Owner

Just a note for my future self. I tried to implement it and the trouble is that AssertJ style tests are currently parsing the value before any configuration can happen -assertThat(json) is parsing the value before any other method can be called.

@lukas-krecan
Copy link
Owner

lukas-krecan commented Jul 4, 2024

Workaround - instead of string, you can provide Jackson JsonNode. Instead of assertThat(jsonString).isEqualTo(jsonString2) you can do assertThat(m.readTree(jsonString)).isEqualTo(m.readTree(jsonString2))

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

No branches or pull requests

2 participants