【问题标题】:Infinite recursion when serializing objects with Jackson and Mockito使用 Jackson 和 Mockito 序列化对象时的无限递归
【发布时间】:2014-05-16 02:21:04
【问题描述】:

我在使用 MockMvc、Mockito 和 Jackson 测试 Spring 控制器时遇到了这个问题,因此我创建了一个简单的类来测试 Jackson 的行为方式。我正在使用 jackson-databind:2.3.1 和 mockito-core:1.9.5。

鉴于这个类:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.Serializable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class Person implements Serializable {
    private String name;
    private int age;

    // Public getters and setters...

    public static void main(String[] args) {
        String name = "Bob";
        int age = 21;
        ObjectMapper objectMapper = new ObjectMapper();

        // attempt serialization with real object
        Person person = new Person();
        person.setName(name);
        person.setAge(age);
        try {
            System.out.println(objectMapper.writeValueAsString(person));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            System.err.println("Failed to serialize real object");
        }

        // attempt serialization with mock object
        Person mockPerson = mock(Person.class);
        when(mockPerson.getName()).thenReturn(name);
        when(mockPerson.getAge()).thenReturn(age);
        try {
            System.out.println(objectMapper.writeValueAsString(mockPerson));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            System.err.println("Failed to serialize mock object.");
        }
    }

Jackson 序列化真实对象没有问题,但是当它尝试序列化模拟对象时会抛出 JsonMappingException。通过代码调试,它反复调用 serializeFields(bean, jgen, provider),卡在内部 Mockito 属性上。

所以,我的问题是:有没有强制杰克逊使用 getter 方法?我在类上尝试了@JsonIgnoreProperties,在字段上尝试了@JsonIgnore,在方法上尝试了@JsonProperty(以不同的组合,但没有成功)。或者,我是否必须编写自己的自定义序列化程序?

谢谢!

【问题讨论】:

  • 他们在代码中!为了便于阅读,我只是将它们排除在外——在这个例子中,它们只是简单地返回私有字段的值。
  • jackson 将使用 getter 方法,但需要遵守命名约定
  • 我向你保证,getter / setter 遵循 Java 命名约定。我什至在类中添加了@JsonIgnoreProperties(ignoreUnknown=true),仍然出现StackOverflowError异常。

标签: java json serialization jackson mockito


【解决方案1】:

这是一个适合您特定情况的解决方案:

首先,您需要创建一个 PersonMixin,因为您无法将所需的注释添加到模拟中。

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonProperty;


@JsonAutoDetect(getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
public interface PersonMixin {

    @JsonProperty
    String getName();

    @JsonProperty
    Integer getAge();
}

现在,像下面的代码一样使用对象映射器,您将得到与序列化真实对象时相同的结果:

Person mockPerson = mock(Person.class);
when(mockPerson.getName()).thenReturn(name);
when(mockPerson.getAge()).thenReturn(age);
objectMapper.addMixInAnnotations(Person.class, PersonMixin.class);
try {
    System.out.println(objectMapper.writeValueAsString(mockPerson));
} catch (JsonProcessingException e) {
    e.printStackTrace();
    System.err.println("Failed to serialize mock object.");
}

【讨论】:

  • 成功了,谢谢!这严格来说是 Mockito 限制吗?我在他们的文档中没有遇到任何关于保留类注释的模拟。
  • @MatthewSerrano 欢迎您!其实你的权利,你不需要mixin!如果您将注释放在 Person 上,它也适用于模拟对象!我什至没有想到这一点,因为我认为 mockito 不会保留注释是理所当然的,而事实上它确实如此!但是我仍然会坚持使用 mixin 上的注释,因为只有模拟才需要注释,因此将它们引入业务对象会引入仅用于模拟目的的复杂性
  • @SalihKardan 很高兴听到这个消息!
【解决方案2】:

这是我的ObjectMapper,无需使用 mixins 即可解决。

映射器会忽略名称中包含“Mockito”的所有成员。

此解决方案避免了对每个序列化对象进行混入,或注释可能无法访问的代码。

运行以下测试成功并输出{"name":"Jonh"}

package test;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import org.mockito.Mockito;

public class AppTest extends Mockito {

    public void testApp() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {

            @Override
            public boolean hasIgnoreMarker(final AnnotatedMember m) {
                return super.hasIgnoreMarker(m) || m.getName().contains("Mockito");
            }
        });

        final String name = "Jonh";

        Person mockPerson = mock(Person.class);
        when(mockPerson.getName()).thenReturn(name);
        System.out.println(mapper.writeValueAsString(mockPerson));
    }


    public static class Person {

        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

【讨论】:

  • 这里的小改进:你应该添加一个而不是设置内省:mapper.getSerializationConfig().withAppendedAnnotationIntrospector(new SkipMockitoAnnotationIntrospector())。在其他情况下,jackson 不会处理其他注释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-07
  • 1970-01-01
  • 2019-10-25
  • 1970-01-01
相关资源
最近更新 更多