【问题标题】:Retrofit 2.0, Jackson and polymorphism: Could not find creator property with name <...>Retrofit 2.0、Jackson 和多态性:找不到名称为 <...> 的创建者属性
【发布时间】:2016-01-09 05:45:53
【问题描述】:

我目前正在使用 RetroFit & Jackson 的 REST API。在以下情况下基于搜索查询检索用户时考虑以下响应 JSON:

找到一个结果

{
    name: "API name",
    results: {
        count: 1
        users: {
            username: "username",
            age: 15
        }
    }
}

找到多个结果

{
    name: "API name",
    results: {
        count: 2,
        users: [{
            username: "username1",
            age: 18
        }, {
            username: "username2",
            age: 19
        }]
    }
}

如您所见,users-property 包含动态 JSON:根据找到的结果,“users”的值可以是用户对象列表,也可以是 1 个用户对象。

因此,我使用多态性设计了我的 Java POJO,如下所示:

public class UserResponse {
    @JsonProperty("name")
    private String apiName

    @JsonProperty("results")
    private AResultList resultList

    //Getters & Setters

    //Constructor
    @JsonCreator
    public UserResponse(@JsonProperty("name") String name, @JsonProperty("results") AResultList r) {
        this.apiName = name;
        this.resultList = r;
    }
}

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "classType"
        )
@JsonSubTypes({
    @JsonSubTypes.Type(value = ResultObject.class, name="ResultObject"),
    @JsonSubTypes.Type(value = ResultList.class, name="ResultList")
})
public abstract class AResultList {
    @JsonProperty("count)
    private long totalCount;

    //Getters & Setters

    //Constructors
    @JsonCreator
    public AResultList(@JsonProperty("count) long count) {
        this.totalCount = count;
    }
}

public class ResultObject extends AResultList {
    @JsonProperty("users")
    private User user;

    //Getters & Setters

    //Constructor
    @JsonCreator
    public ResultObject(@JsonProperty("count) long count, @JsonProperty("users") User u) {
        super(count);

        this.user = u;
    }
}

public class ResultList extends AResultList {
    @JsonProperty("users")
    private List<User> users;

    //Getters & Setters

    //Constructor
    @JsonCreator
    public ResultObject(@JsonProperty("count) long count, @JsonProperty("users") List<User> u) {
        super(count);

        this.users = u;
    }
}

public class User {
    @JsonProperty("username")
    private String username;

    @JsonProperty("age")
    private long userAge;

    //Getters & Setters

    //Constructor
    @JsonCreator
    public User(@JsonProperty("username") String u, @JsonProperty("age") long a) {
        this.userAge = a;
        this.username = u;
    }
}

供您参考:用于实例化 RetroFit 的 sn-p

ObjectMapper o = new ObjectMapper();

retrofit = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(JacksonConverterFactory.create(o))
                .client(okClient)
                .build();

但是,尝试检索此信息会导致以下错误:

com.fasterxml.jackson.databind.JsonMappingException: Could not find creator property with name 'count' (in class org.namespace.AResultList)
at [Source: java.io.InputStreamReader@41cd2648; line: 1, column: 1]

我已经被这个“动态数据”问题困扰了 2 天了。已经尝试使用 GSON 库和许多其他东西,但无济于事。所以我想问一下:
- 为什么杰克逊会造成这种情况?这是一个错误吗?一些用户已经问过这个问题,但提供的解决方案不适用于我的情况。
- 这是处理动态数据的正确方法吗?

我已经完美地测试了我的代码,没有使用多态类(并且只从 API 中检索 1 个用户)和对象映射。问题是由多态性引起的,但我不知道如何解决。

【问题讨论】:

    标签: java android json jackson retrofit


    【解决方案1】:

    您在 Jackson 注释中指定的多态性配置表明 {"classType":"ResultObject",...}{"classType":"ResultList",...} 将出现在 JSON 中 - 但事实并非如此。我不确定您收到错误的确切原因,但它似乎正在寻找抽象类的创建者,因为没有类型属性。

    [对于多态性,Jackson 需要从 JSON 中读取一些内容以确定此时要反序列化的 bean 类型:您实际上并没有,只有 users 的数组/对象性。因此,我认为 Jackson 的多态性支持不太适合这种情况]

    事实上,要允许属性获取单个项目或项目列表,您只需要启用ACCEPT_SINGLE_VALUE_AS_ARRAY 反序列化功能。但是,这需要全局启用,似乎没有办法将其定位到特定属性。

    public static final class UserResponse {
        public String name;
        public Results results;
    
        public static final class Results {
            public int count;
            public List<User> users;
        }
    
        public static final class User {
            public String username;
            public int age;
        }
    }
    
    @Test
    public void reads_single_result() throws Exception {
        ObjectReader reader = new ObjectMapper().reader(UserResponse.class)
                .with(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
                .with(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES).with(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
        UserResponse response = reader.readValue("{ name: 'API name', results: { count: 1,"
                + " users: { username: 'username', age: 15 } } }");
        assertThat(response.results.users, iterableWithSize(1));
        assertThat(response.results.users.get(0).username, equalTo("username"));
    }
    
    @Test
    public void reads_two_results() throws Exception {
        ObjectReader reader = new ObjectMapper().reader(UserResponse.class)
                .with(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
                .with(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES).with(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
        UserResponse response = reader.readValue("{ name: 'API name', results: { count: 2,"
                + " users: [{ username: 'username1', age: 18 }, { username: 'username2', age: 19 }] } }");
        assertThat(response.results.users, iterableWithSize(2));
        assertThat(response.results.users.get(0).username, equalTo("username1"));
        assertThat(response.results.users.get(1).username, equalTo("username2"));
    }
    

    哦,如果你想摆脱其中无用的结果对象,你可以使用转换器来做到这一点:

    public static final class UserResponseWithConverter {
        public String name;
        @JsonProperty("results")
        @JsonDeserialize(converter = ConvertResultsToUserList.class)
        public List<User> users;
    
        public static final class Results {
            public int count;
            public List<User> users;
        }
    
        public static final class User {
            public String username;
            public int age;
        }
    
        public static final class ConvertResultsToUserList extends StdConverter<Results, List<User>> {
            @Override
            public List<User> convert(Results value) {
                return value.users;
            }
        }
    }
    

    配置正确的序列化留给读者练习;)

    【讨论】:

    • 非常感谢!我只需要“ACCEPT_SINGLE_VALUE_AS_ARRAY”序列化选项。您对“转换”和剥离无用结果属性的第二个答案甚至对我必须转换的其他一些数据派上用场。
    猜你喜欢
    • 2012-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-22
    • 1970-01-01
    • 1970-01-01
    • 2019-02-22
    相关资源
    最近更新 更多