Khojiakbar
Khojiakbar

Reputation: 579

Jackson "MismatchedInputException: No Object Id found for an instance" when using @JsonIdentityInfo

First of all, I'm trying to prevent recursion in JSON, when I'm getting User entity. To do this I've added @JsonIdentityInfo annotation to my User class. It works as expected for serialization (when I get User), but on deserialization (on registering User) Jackson returns this:

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: No Object Id found for an instance of `.entity.User`, to assign to property 'id'; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: No Object Id found for an instance of `.entity.User`, to assign to property 'id'
 at [Source: (PushbackInputStream); line: 13, column: 1]]

User.kt

@Entity
@Table(name = "user")
@Audited
@EntityListeners(AuditingEntityListener::class)
@Validated
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator::class, property = "id", scope = Int::class)
data class User(

        @field:Id
        @field:GeneratedValue(strategy = GenerationType.IDENTITY)
        var id: Int,

        @Valid
        @Size(
                min = 4,
                message = "Username '\${validatedValue}' should be at least {min} characters long"
        )
        @NotEmpty(message = "asd")
        var username: String = "",

        ...

        @field:OneToOne(cascade = [CascadeType.ALL], optional = true)
        var userInfo: UserInfo? = null

)

JSON I'm sending for deserialization (Works when @JsonIdentityInfo is removed):

{
    "username": "qwert",
    "password": "tyui",
    "email": "asdasd",
    "phone": ""
}

Method used for registration:

    @PostMapping("/users/signup")
    fun addUser(@RequestBody @Valid user: User): JSendResponse<Unit> {

        user.password = passwordEncoder.encode(user.password)
        userService.save(user)

        return JSendResponse(data = Unit)
    }

UPD: The solution to this problem was to explicitly set id field in Json because it's generated on persistence level. But I don't want to set it explicitly, because frontend doesn't know the id of the new object.

Upvotes: 3

Views: 1628

Answers (2)

Cactusroot
Cactusroot

Reputation: 1058

For anyone searching:

This issue may be caused when combining @JsonIdentityInfo.


In case you are using @JsonTypeInfo:

  • use @JsonTypeName together with @JsonSubTypes
  • wrap potential generic fields into a class extending from it's type

If you are using non-empty constructors:

  • try using (private) empty ones annotated with @JsonCreator (especially in referenced classes) and assign any value to final attributes (which jackson will override nevertheless)

Jackson throws very unrelated and illogical exceptions that are caused trying to deserialize an instance inside of the exact same instance, seeing them as different objects.


Tested in java, but shouldn't differ

Upvotes: 0

Ismael Sarmento
Ismael Sarmento

Reputation: 894

JsonIdentityInfo is used to avoid circular reference, that causes a stackoverflow. Jackson is supposed to assign an 'id' when it finds this type of references.

It seems some versions of jackson have a bug on this functionality: github.com/FasterXML/jackson-databind/issues/1367 So you should either try to change version of jackson, or do the following alternatives:

Some alternatives are:

Use the annotations JsonManagedReference and JsonBackReference on the fields that reference each other; in your case, 'user' and 'userInfo'. JsonManagedReference is the side that gets (de)serialized normally. JsonBackReference is the back part of reference and won't be (de)serialized. Make sure to set the 'back reference' part after deserialized.

If, for example, your UserInfo property is the managed ref, inside the User class, you should have the following results:

class User {
    Long id;
    @JsonManagedReference
    UserInfo userInfo;
}

class UserInfo {
    String nickname;
    String moto;
    @JsonBackReference
    User user;
    ...
}

// OUTPUT EXAMPLE
public static void main(String[] args) throws JsonProcessingException {
    UserInfo info = new UserInfo("johndoe","it works", null);
    User user = new User(1L, info);
    info.setUser(user);
    ObjectMapper om = new ObjectMapper();
    System.out.println("user = " + om.writeValueAsString(user));
    System.out.println("userinfo = " + om.writeValueAsString(info));
}

OUTPUT

user = {
   "id":1,
   "userInfo":{
     "nickname":"johndoe",
     "moto":"it works"
    }
}
userinfo = {
   "nickname":"johndoe",
   "moto":"it works"
}

Another alternatice is to Create a custom serializer and a custom deserializer for your entities. This is the most powerful way to (de)serialize; you can have both sides (de)serialized and even add custom properties.

@JsonDeserialize(using = UserDeserializer.class)
@JsonSerialize(using = UserSerializer.class)
data class User(
    ...
)

To create custom (de)serializers, you can follow these tutorials:

Upvotes: 1

Related Questions