13

I have a controller that accepts ObjectNode as @RequestBody.

That ObjectNode represents json with some user data

{
    "given_name":"ana",
    "family_name": "fabry",
    "email": "[email protected]",
    "password": "mypass",
    "gender": "FEMALE"
}

Controller.java

@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    public JsonNode createUser(@RequestBody ObjectNode user){
        return userService.addUser(user);
 }

I want to get user as ObjectNode convert it to Java POJO save it to database and again return it as JsonNode.

UserServiceImpl.java

    private final UserRepository userRepository;
    private final UserMapper userMapper;

    @Override
    public JsonNode addUser(@RequestBody ObjectNode user) {
        try {
            return userMapper.fromJson(user)
                    .map(r -> {
                        final User created = userRepository.save(r);
                        return created;
                    })
                    .map(userMapper::toJson)
                    .orElseThrow(() -> new ResourceNotFoundException("Unable to find user"));
        } catch (RuntimeException re) {
            throw re;
        }
    }

To convert ObjectNode to POJO

I did this in my UserMapper class:

public Optional<User> fromJson(ObjectNode jsonUser) {
  User user = objectMapper.treeToValue(jsonUser, User.class);
}

Also, to write object to JsonNode I did this:

public JsonNode toJson(User user) {
        ObjectNode node = objectMapper.createObjectNode();
        node.put("email", user.email);
        node.put("password", user.password);
        node.put("firstName", user.firstName);
        node.put("lastName", user.firstName);
        node.put("gender", user.gender.value);
        node.put("registrationTime", user.registrationTime.toString());
        return node;
}

User.java

@Document(collection = "user")
@Builder
@AllArgsConstructor
public class User {

    @Indexed(unique = true)
    public final String email;
    @JsonProperty("password")
    public final String password;
    @JsonProperty("firstName")
    public final String firstName;
    @JsonProperty("lastName")
    public final String lastName;
    @JsonProperty("gender")
    public final Gender gender;
    @JsonProperty("registrationTime")
    public final Instant registrationTime;

    public static User createUser(
            String email,
            String password,
            String firstName,
            String lastName,
            Gender gender,
            Instant registrationTime){
        return new User(email, password, firstName, lastName, gender, registrationTime);
    }
}

When I run my application, this is the error I am receiving:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.domain.User` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

I have read about the error, and it seems this error occurs because Jackson library doesn't know how to create a model which doesn't have an empty constructor and the model contains a constructor with parameters which I annotated its parameters with @JsonProperty("fieldName"). But even after applying @JsonProperty("fieldName") I am still getting the same error.

I have defined ObjecatMapper as Bean

    @Bean
    ObjectMapper getObjectMapper(){
        return new ObjectMapper();
    }

What am I missing here?

7

3 Answers 3

7

I could reproduce the exception. Then I added an all-args constructor with each parameter annotated with the right @JsonProperty.

@JsonCreator
public User( 
    @JsonProperty("email") String email,
    @JsonProperty("password") String password,
    @JsonProperty("firstName") String firstName,
    @JsonProperty("lastName") String lastName,
    @JsonProperty("gender") String gender,
    @JsonProperty("registrationTime") Instant registrationTime){
            super();
            this.email = email;
            this.password = password;
            this.firstName = firstName;
            this.lastName = lastName;
            this.gender = gender;
            this.registrationTime = registrationTime;
}

Now, it creates the instance, but I get other mapping errors (Unrecognized field "given_name") which you should be able to resolve.

Sign up to request clarification or add additional context in comments.

3 Comments

Okay, thank you. Yes, I did like you now. But I am getting null pointer exception from method toJson on this line of code node.put("registrationTime", user.registrationTime.toString()); do you maybe know how can I handle it?
That may be because the input doesn't have registrationTime in it, so null is being to the field. You may give it a default value in the constructor above, if null is passed to it.
So maybe the best is to put it as Optional.
3

Register Jackson ParameterNamesModule, which will automatically map JSON attributes to the corresponding constructor attributes and therefore will allow you to use immutable classes.

1 Comment

How can add this to Bean as I am defining my object mapper as bean see update of my question
1

The error happened in scala due to missing the name of keys in the constructor.

I missed specifying the names (example currency) in the snippet below - @JsonProperty("currency")

For converting json to case-class using scala and jackson, we can do it in the following way -

Add the jackson dependency in maven/sbt. If you are using sbt, then add the below entry in build.sbt

libraryDependencies += "com.fasterxml.jackson.core" % "jackson-core" % "2.12.5"

Define the case class -

import com.fasterxml.jackson.annotation.{JsonCreator, JsonProperty}

import java.util.Currency

@JsonCreator
case class TopUp (
                   @JsonProperty("cardId") cardId : Long,
                   @JsonProperty("amount") amount : BigDecimal,
                   @JsonProperty("currency") currency: Currency
                 )
object TopUp  {


}

In the main method -

import com.fasterxml.jackson.databind.ObjectMapper
import models.oyster.mappers.request.TopUp

object App {
  def main(args : Array[String]): Unit ={
    val tmp =
      """
        |{
        |    "cardId": 718908976540,
        |    "amount": 30.00,
        |    "currency": "GBP"
        |
        |}
        |""".stripMargin

    val objectMapper = new ObjectMapper()
    val inputParsed = objectMapper.readValue(tmp, classOf[TopUp])
    println("parsed input :: " + inputParsed)

  }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.