杰克逊:在反序列化时保持对地图值中键的引用

时间:2017-03-13 12:08:48

标签: java json jackson

我有以下JSON,其中包含从用户ID到用户详细信息的映射:

{
    "users": {
        "john": { "firstName": "John", "lastName": "Doe" },
        "mark": { "firstName": "Mark", "lastName": "Smith" }
    }
}

我正在使用以下代码将JSON反序列化为Java对象:

class User {
    public String userID;

    public String firstName;
    public String lastName;
}

public class Users {
    public Map<String, User> users;

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Reader source = Files.newBufferedReader(Paths.get("test.json"));
        Users all = mapper.readValue(source, Users.class);
        // ...
    }
}

反序列化后,我希望将字段User.userID设置为users地图中的相应密钥。

例如all.users.get("john").userID应为"john"

我该怎么做?

3 个答案:

答案 0 :(得分:1)

为User对象创建自定义反序列化器并将其用于Map。这是一个完整的例子:

@Test
public void test() throws JsonParseException, JsonMappingException, IOException {
    ObjectMapper mapper = new ObjectMapper();

    Data data = mapper.readValue("{\"users\": {\"John\": {\"id\": 20}, \"Pete\": {\"id\": 30}}}", Data.class);

    assertEquals(20, data.users.get("John").id);
    assertEquals(30, data.users.get("Pete").id);
    assertEquals("John", data.users.get("John").name);
    assertEquals("Pete", data.users.get("Pete").name);
}

public static class Data {
    @JsonDeserialize(contentUsing = Deser.class)
    public Map<String, User> users;
}

public static class User {
    public String name;
    public int id;
}

public static class Deser extends JsonDeserializer<User> {

    @Override
    public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String name = ctxt.getParser().getCurrentName();

        User user = p.readValueAs(User.class);

        user.name = name;  // This copies the key name to the user object

        return user;
    }
}

答案 1 :(得分:0)

最简单的解决方法是为需要映射键的类实现自定义反序列化器(请参见john16384's answer)。但是,如果您的JSON中有多个具有不同值类型的映射,这将很麻烦,因为每种类型都需要一个反序列化器。

在这种情况下,有一个更好的解决方案:我将创建一个自定义@JsonMapKey注释来标记映射键的目标属性,然后注册一个通用的自定义反序列化器,以处理所有出现的注释。这些是您需要的部分:

自定义@JsonMapKey注释:

/**
 * Annotation used to indicate that the annotated property shall be deserialized to the map key of
 * the current object. Requires that the object is a deserialized map value.
 * 
 * Note: This annotation is not a standard Jackson annotation. It will only work if this is
 * explicitly enabled in the {@link ObjectMapper}.
 */
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonMapKey {

}

处理@JsonMapKey批注的自定义反序列化器:

public class JsonMapKeyDeserializer extends DelegatingDeserializer {

    private static final long serialVersionUID = 1L;
    private BeanDescription beanDescription;

    public JsonMapKeyDeserializer(JsonDeserializer<?> delegate, BeanDescription beanDescription) {
        super(delegate);
        this.beanDescription = beanDescription;
    }

    @Override
    protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
        return new JsonMapKeyDeserializer(newDelegatee, beanDescription);
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String mapKey = p.getCurrentName();
        Object deserializedObject = super.deserialize(p, ctxt);

        // set map key on all fields annotated with @JsonMapKey
        for (BeanPropertyDefinition beanProperty : beanDescription.findProperties()) {
            AnnotatedField field = beanProperty.getField();
            if (field != null && field.getAnnotation(JsonMapKey.class) != null) {
                field.setValue(deserializedObject, mapKey);
            }
        }
        return deserializedObject;
    }
}

ObjectMapper中注册自定义反序列化器:

private static void registerJsonMapKeyAnnotation(ObjectMapper objectMapper) {
    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier() {
        @Override
        public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, 
                BeanDescription beanDescription, JsonDeserializer<?> originalDeserializer) {
            return new JsonMapKeyDeserializer(originalDeserializer, beanDescription);
        }
    });
    objectMapper.registerModule(module);
}

然后,您只需注释要用于地图键的字段...

class User {
    @JsonMapKey
    public String userID;

    public String firstName;
    public String lastName;
}

...并使用准备好的ObjectMapper反序列化JSON:

Users all = registerJsonMapKeyAnnotation(new ObjectMapper()).readValue(source, Users.class);

答案 2 :(得分:-1)

首先创建ObjectMapper class对象,而不是configure

尝试以下一个。

示例代码

Map<K, V> map;
ObjectMapper mapper = new ObjectMapper();
mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
map = mapper.readValue(jsonStr, new TypeReference<Map<K, V>>() {});

可以使用Map获取值。