例如,我们的目标类型为Info
,定义如下
class Info {
Person person;
// a dozen of other properties
// getters and setters
}
class Person {
String name;
int age;
// getters and setters
}
我们的地图就像Collections.singletonMap("person", "{\"name\": \"nick\", \"age\": 18}")
如何将此Map转换为Info
对象? (为Person
添加一个构造函数或一个一个地解析值是不可行的,因为实际上有很多属性(例如Person)。)
尝试将杰克逊与objectMapper.convertValue
一起使用,但它抛出异常,消息为no String-argument constructor/factory method to deserialize from String value
答案 0 :(得分:1)
Jackson的ObjectMapper
知道如何将Map
转换为POJO
,但是它无法将带有JSON文本的字符串映射到POJO,因此您首先必须将JSON文本解析为某种东西通用(Map
或JsonNode
树)。
基本上,将您的Map<String, String>
转换为Map<String, JsonNode>
。
Map<String, String> data = Collections.singletonMap("person", "{\"name\": \"nick\", \"age\": 18}");
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> dataTree = new HashMap<>();
for (Entry<String, String> entry : data.entrySet())
dataTree.put(entry.getKey(), mapper.readTree(entry.getValue()));
Info info = mapper.convertValue(dataTree, Info.class);
System.out.println("name = " + info.getPerson().getName());
System.out.println("age = " + info.getPerson().getAge());
输出
name = nick
age = 18
答案 1 :(得分:1)
如果您确实有一个Map<String, String>
,键是您的字段名称,值是一个json
结构,那么没有其他机会了:
switch (map.getKey()) {
case "person":
Person person = mapper.readValue(map.getValue(), Person.class);
info.setPerson(person);
break;
//TODO add your other Map-Keys here
}
但是,如果您具有正常的json结构,例如:
{
"person": {
"name": "nick",
"age": 18
}
}
那么您可以简单地:
Info info = mapper.readValue(json, Info.class);
答案 2 :(得分:0)
您可以使用Google创建的用于处理json结构的Gson库轻松实现此目的。
Map<String, String> data = Collections.singletonMap("person", "{\"name\": \"nick\", \"age\": 18}");
Person person = new Gson.fromJson(data.get("person"), Person.class);
Info info = new Info();
info.setPerson(person);
答案 3 :(得分:0)
好的,看完源代码之后,我想出了一个解决方案:
class StringBeanDeserializer extends BeanDeserializer {
private ObjectMapper objectMapper;
public StringBeanDeserializer(BeanDeserializerBuilder builder, BeanDescription beanDesc, BeanPropertyMap properties, Map<String, SettableBeanProperty> backRefs, HashSet<String> ignorableProps, boolean ignoreAllUnknown, boolean hasViews, ObjectMapper objectMapper) {
super(builder, beanDesc, properties, backRefs, ignorableProps, ignoreAllUnknown, hasViews);
this.objectMapper = objectMapper == null ? new ObjectMapper() : objectMapper;
}
@Override
public Object deserializeFromString(JsonParser p, DeserializationContext ctxt) throws IOException {
if (this._beanType.isTypeOrSubTypeOf(CharSequence.class)) {
return super.deserializeFromString(p, ctxt);
}
return objectMapper.readValue(p.getText(), this._beanType.getRawClass());
}
}
class StringBeanDeserializerBuilder extends BeanDeserializerBuilder {
private ObjectMapper objectMapper;
public StringBeanDeserializerBuilder(BeanDescription beanDesc, DeserializationContext ctxt) {
super(beanDesc, ctxt);
}
public StringBeanDeserializerBuilder(BeanDescription beanDesc, DeserializationContext ctxt, ObjectMapper objectMapper) {
this(beanDesc, ctxt);
this.objectMapper = objectMapper;
}
protected StringBeanDeserializerBuilder(BeanDeserializerBuilder src) {
super(src);
}
/**
* Method for constructing a {@link BeanDeserializer}, given all
* information collected.
*/
public JsonDeserializer<?> build()
{
Collection<SettableBeanProperty> props = _properties.values();
_fixAccess(props);
BeanPropertyMap propertyMap = BeanPropertyMap.construct(props,
_config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES),
_collectAliases(props));
propertyMap.assignIndexes();
// view processing must be enabled if:
// (a) fields are not included by default (when deserializing with view), OR
// (b) one of properties has view(s) to included in defined
boolean anyViews = !_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION);
if (!anyViews) {
for (SettableBeanProperty prop : props) {
if (prop.hasViews()) {
anyViews = true;
break;
}
}
}
// one more thing: may need to create virtual ObjectId property:
if (_objectIdReader != null) {
/* 18-Nov-2012, tatu: May or may not have annotations for id property;
* but no easy access. But hard to see id property being optional,
* so let's consider required at this point.
*/
ObjectIdValueProperty prop = new ObjectIdValueProperty(_objectIdReader, PropertyMetadata.STD_REQUIRED);
propertyMap = propertyMap.withProperty(prop);
}
return new StringBeanDeserializer(this,
_beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown,
anyViews, objectMapper);
}
}
public class StringBeanDeserializeFactory extends BeanDeserializerFactory {
private ObjectMapper objectMapper = new ObjectMapper();
/**
* Globally shareable thread-safe instance which has no additional custom deserializers
* registered
*/
public final static BeanDeserializerFactory instance = new StringBeanDeserializeFactory(
new DeserializerFactoryConfig());
public StringBeanDeserializeFactory(DeserializerFactoryConfig config) {
super(config);
}
/**
* Overridable method that constructs a {@link BeanDeserializerBuilder}
* which is used to accumulate information needed to create deserializer
* instance.
*/
protected BeanDeserializerBuilder constructBeanDeserializerBuilder(DeserializationContext ctxt,
BeanDescription beanDesc) {
return new StringBeanDeserializerBuilder(beanDesc, ctxt, objectMapper);
}
}
ObjectMapper objectMapper = new ObjectMapper(null, null, new DefaultDeserializationContext.Impl(StringBeanDeserializeFactory.instance));
Map<String, String> map = ImmutableMap.of("person", objectMapper.writeValueAsString(new Person("nick", 25)),
"raw", "test");
System.out.println(objectMapper.convertValue(map, Data.class));