Джексон "MismatchedInputException: не найден идентификатор объекта для экземпляра" при использовании @JsonIdentityInfo - PullRequest
1 голос
/ 08 апреля 2020

Прежде всего, я пытаюсь предотвратить рекурсию в JSON, когда получаю User сущность. Для этого я добавил @JsonIdentityInfo аннотацию к моему User классу. Он работает как ожидается для сериализации (когда я получаю User), но при десериализации (при регистрации User) Джексон возвращает это:

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 Я отправляю на десериализацию (работает при удалении @JsonIdentityInfo):

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

Метод, использованный для регистрации:

    @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: Решением этой проблемы было явное задание поля id в Json, поскольку оно генерируется на уровне постоянства. Но я не хочу устанавливать это явно, потому что веб-интерфейс не знает id нового объекта.

1 Ответ

1 голос
/ 14 апреля 2020

JsonIdentityInfo используется, чтобы избежать циклической ссылки, которая вызывает переполнение стека. Предполагается, что Джексон назначит идентификатор при обнаружении ссылок этого типа.

Кажется, в некоторых версиях Джексона есть ошибка в этой функции: github.com / FasterXML / jackson-databind / Issues / 1367 Таким образом, вы должны либо попытаться изменить версию Джексона, либо сделать следующие альтернативы:

Некоторые альтернативы:

Используйте аннотации JsonManagedReference и JsonBackReference для полей, которые ссылаются друг на друга; в вашем случае 'user' и 'userInfo'. JsonManagedReference - это сторона, которая сериализуется (де) сериализованно. JsonBackReference является задней частью ссылки и не будет (де) сериализован. Обязательно установите часть «обратная ссылка» после десериализации.

Если, например, вашим свойством UserInfo является управляемый ref, то внутри класса User вы должны получить следующие результаты:

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"
}

Другой альтернативой является Создание настраиваемого сериализатора и настраиваемого десериализатора для ваших сущностей. Это самый мощный способ (де) сериализации; Вы можете сериализовать обе стороны (де) и даже добавлять пользовательские свойства.

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

Для создания настраиваемых (де) сериализаторов вы можете следовать следующим учебникам:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...