使用 Jackson 将 json 嵌入式节点作为值反序列化到 Map 中
Deserializing json embedded node as value into Map using Jackson
亲爱的朋友们,我需要你的建议。
我正在尝试为地图实现自定义 Jackson
反序列化器,但遇到了困难。作为输入数据,我有以下 json:
{
"someMap": {
"one_value": "1",
"another_value: "2"
},
"anotherMap": "{\"100000000\": 360000,\"100000048\": 172800,\"100000036\": 129600,\"100000024\": 86400,\"100000012\": 43200}"
}
正如您在第二种情况下看到的那样,它在节点值内有 json 映射(我是故意这样做的。因为我想替换环境变量中的值:"anotherMap": "${SOME_MAP:-{\"100000000\": 360000,\"100000048\": 172800,\"100000036\": 129600,\"100000024\": 86400,\"100000012\": 43200}}"
).据我了解,我必须以某种方式区分这 2 个地图反序列化流程。因此,对于第一张地图,我需要为第二张地图使用默认的地图反序列化器,即自定义地图,以便从值中正确解析地图。目前我写了代码来做到这一点:
// invokation code
new ObjectMapper().registerModule(new ConfigModule()).readValue(is, ConfigModuleTestConfigWrapper.class);
// module code
public class ConfigModule extends SimpleModule {
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addDeserializers(new Deserializers.Base() {
@Override
public JsonDeserializer<?> findMapDeserializer(MapType type, DeserializationConfig config, BeanDescription beanDesc,
KeyDeserializer keyDeserializer, TypeDeserializer elementTypeDeserializer,
JsonDeserializer<?> elementDeserializer) throws JsonMappingException {
return new MapPropertyDeserializer(type);
}
});
}
private static class MapPropertyDeserializer extends StdScalarDeserializer<Map<String, Integer>> {
MapPropertyDeserializer(MapType type) {
super(type);
}
@Override
public Map<String, Integer> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.readValueAsTree();
if (node == null || node.isContainerNode()) {
// return <default jackson deserializer>
}
System.out.println("isContainerNode ?: " + node.isContainerNode());
System.out.println("isValueNode ?: " + node.isValueNode());
// some parsing flow goes below
JsonNode valueNode = node.get(1);
valueNode.asText();
return new HashMap<>();
}
}
// bean description
@JsonIgnoreProperties
public class ConfigSubstitutorModuleTestConfigWrapper {
private final Map<String, String> someMap;
private final Map<String, Integer> anotherMap;
@JsonCreator
public ConfigSubstitutorModuleTestConfigWrapper(
@JsonProperty("someMap") Map<String, String> someMap,
@JsonProperty("anotherMap") Map<String, Integer> anotherMap
) {
this.someMap = someMap;
this.anotherMap = anotherMap;
}
public Map<String, String> getSomeMap() {
return someMap;
}
public Map<String, Integer> getAnotherMap() {
return anotherMap;
}
}
问题是(据我了解:))我不知道如何从反序列化方法return默认映射反序列化器。
有人知道我可以在那里做什么来实现预期目标吗?
终于接受了解决方案来解决它:
1) 创建解串器 class:
/**
* The target of that deserializer is to do two-step deserialization.
* At first it just reads string and then does second deserialization in the proper {@link Map} type once string substitution done.
* <p>
* Note! In order to get object mapper reference you have to set it first on object mapper initialization stage:
* </p>
* <pre>
* objectMapper.setInjectableValues(new InjectableValues.Std().addValue(OBJECT_MAPPER_VALUE_ID, objectMapper));
* </pre>
*/
public class ValueAsMapDeserializer extends JsonDeserializer<Map> implements ContextualDeserializer {
public static final String OBJECT_MAPPER_VALUE_ID = "objectMapper";
static final String VALUE_PREFIX = "$|";
static final String VALUE_SUFFIX = "|";
private JavaType keyType;
private JavaType valueType;
@Override
public JsonDeserializer<?> createContextual(final DeserializationContext ctxt,
final BeanProperty property) throws JsonMappingException {
JavaType filedType = property.getType();
this.keyType = filedType.getKeyType();
this.valueType = filedType.getContentType();
return this;
}
@Override
public Map deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
// Can't use constructor init there because of intention to use that deserializer using annotation
// Also such tricky thing as 'injectable values' was used cause of no way to get the reference to object mapper from deserialization context out of the box
ObjectMapper objectMapper = (ObjectMapper) ctxt.findInjectableValue(OBJECT_MAPPER_VALUE_ID, null, null);
final Optional<String> substitutedValue = Substitutor.create(jp, VALUE_PREFIX, VALUE_SUFFIX).substitute();
MapType mapType = objectMapper.getTypeFactory().constructMapType(Map.class, keyType, valueType);
return objectMapper.readValue(substitutedValue.orElseThrow(() -> new RuntimeException("Failed to parse the value as map")), mapType);
}
}
2) 标记 bean 字段以使用该反序列化器:
@JsonDeserialize(using = ValueAsMapDeserializer.class)
private final Map<String, Integer> anotherMap;
尝试分两步进行反序列化:
package stack43844461;
import java.io.IOException;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class HowToConvertJsonStringToMap {
@Test
public void json() throws JsonParseException, JsonMappingException, IOException {
String jsonInString = "{\"someMap\":"
+ " {\"one_value\": \"1\","
+ "\"another_value\": \"2\"},"
+ "\"anotherMap\": "
+ "\"{\\"100000000\\": 360000,"
+ "\\"100000048\\": 172800,"
+ "\\"100000036\\": 129600,"
+ "\\"100000024\\": 86400,"
+ "\\"100000012\\": 43200}\"}";
ObjectMapper mapper = new ObjectMapper();
// Step 1: Read everything into one object.
Map<String, Object> all = mapper.readValue(jsonInString, Map.class);
// Step 2: Get your "normal" data into one object
Map<String, Object> someMap=(Map<String, Object>) all.get("someMap");
// Step 3: Get your "embedded" data from your object
String anotherMapStr = (String) all.get("anotherMap");
// Step 4: Deserialize embedded data
Map<String, Object> anotherMap = mapper.readValue(anotherMapStr, Map.class);
System.out.println(anotherMap);
System.out.println(someMap);
}
}
打印:
{100000000=360000, 100000048=172800, 100000036=129600, 100000024=86400, 100000012=43200}
{one_value=1, another_value=2}
亲爱的朋友们,我需要你的建议。
我正在尝试为地图实现自定义 Jackson
反序列化器,但遇到了困难。作为输入数据,我有以下 json:
{
"someMap": {
"one_value": "1",
"another_value: "2"
},
"anotherMap": "{\"100000000\": 360000,\"100000048\": 172800,\"100000036\": 129600,\"100000024\": 86400,\"100000012\": 43200}"
}
正如您在第二种情况下看到的那样,它在节点值内有 json 映射(我是故意这样做的。因为我想替换环境变量中的值:"anotherMap": "${SOME_MAP:-{\"100000000\": 360000,\"100000048\": 172800,\"100000036\": 129600,\"100000024\": 86400,\"100000012\": 43200}}"
).据我了解,我必须以某种方式区分这 2 个地图反序列化流程。因此,对于第一张地图,我需要为第二张地图使用默认的地图反序列化器,即自定义地图,以便从值中正确解析地图。目前我写了代码来做到这一点:
// invokation code
new ObjectMapper().registerModule(new ConfigModule()).readValue(is, ConfigModuleTestConfigWrapper.class);
// module code
public class ConfigModule extends SimpleModule {
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addDeserializers(new Deserializers.Base() {
@Override
public JsonDeserializer<?> findMapDeserializer(MapType type, DeserializationConfig config, BeanDescription beanDesc,
KeyDeserializer keyDeserializer, TypeDeserializer elementTypeDeserializer,
JsonDeserializer<?> elementDeserializer) throws JsonMappingException {
return new MapPropertyDeserializer(type);
}
});
}
private static class MapPropertyDeserializer extends StdScalarDeserializer<Map<String, Integer>> {
MapPropertyDeserializer(MapType type) {
super(type);
}
@Override
public Map<String, Integer> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.readValueAsTree();
if (node == null || node.isContainerNode()) {
// return <default jackson deserializer>
}
System.out.println("isContainerNode ?: " + node.isContainerNode());
System.out.println("isValueNode ?: " + node.isValueNode());
// some parsing flow goes below
JsonNode valueNode = node.get(1);
valueNode.asText();
return new HashMap<>();
}
}
// bean description
@JsonIgnoreProperties
public class ConfigSubstitutorModuleTestConfigWrapper {
private final Map<String, String> someMap;
private final Map<String, Integer> anotherMap;
@JsonCreator
public ConfigSubstitutorModuleTestConfigWrapper(
@JsonProperty("someMap") Map<String, String> someMap,
@JsonProperty("anotherMap") Map<String, Integer> anotherMap
) {
this.someMap = someMap;
this.anotherMap = anotherMap;
}
public Map<String, String> getSomeMap() {
return someMap;
}
public Map<String, Integer> getAnotherMap() {
return anotherMap;
}
}
问题是(据我了解:))我不知道如何从反序列化方法return默认映射反序列化器。
有人知道我可以在那里做什么来实现预期目标吗?
终于接受了解决方案来解决它:
1) 创建解串器 class:
/**
* The target of that deserializer is to do two-step deserialization.
* At first it just reads string and then does second deserialization in the proper {@link Map} type once string substitution done.
* <p>
* Note! In order to get object mapper reference you have to set it first on object mapper initialization stage:
* </p>
* <pre>
* objectMapper.setInjectableValues(new InjectableValues.Std().addValue(OBJECT_MAPPER_VALUE_ID, objectMapper));
* </pre>
*/
public class ValueAsMapDeserializer extends JsonDeserializer<Map> implements ContextualDeserializer {
public static final String OBJECT_MAPPER_VALUE_ID = "objectMapper";
static final String VALUE_PREFIX = "$|";
static final String VALUE_SUFFIX = "|";
private JavaType keyType;
private JavaType valueType;
@Override
public JsonDeserializer<?> createContextual(final DeserializationContext ctxt,
final BeanProperty property) throws JsonMappingException {
JavaType filedType = property.getType();
this.keyType = filedType.getKeyType();
this.valueType = filedType.getContentType();
return this;
}
@Override
public Map deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
// Can't use constructor init there because of intention to use that deserializer using annotation
// Also such tricky thing as 'injectable values' was used cause of no way to get the reference to object mapper from deserialization context out of the box
ObjectMapper objectMapper = (ObjectMapper) ctxt.findInjectableValue(OBJECT_MAPPER_VALUE_ID, null, null);
final Optional<String> substitutedValue = Substitutor.create(jp, VALUE_PREFIX, VALUE_SUFFIX).substitute();
MapType mapType = objectMapper.getTypeFactory().constructMapType(Map.class, keyType, valueType);
return objectMapper.readValue(substitutedValue.orElseThrow(() -> new RuntimeException("Failed to parse the value as map")), mapType);
}
}
2) 标记 bean 字段以使用该反序列化器:
@JsonDeserialize(using = ValueAsMapDeserializer.class)
private final Map<String, Integer> anotherMap;
尝试分两步进行反序列化:
package stack43844461;
import java.io.IOException;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class HowToConvertJsonStringToMap {
@Test
public void json() throws JsonParseException, JsonMappingException, IOException {
String jsonInString = "{\"someMap\":"
+ " {\"one_value\": \"1\","
+ "\"another_value\": \"2\"},"
+ "\"anotherMap\": "
+ "\"{\\"100000000\\": 360000,"
+ "\\"100000048\\": 172800,"
+ "\\"100000036\\": 129600,"
+ "\\"100000024\\": 86400,"
+ "\\"100000012\\": 43200}\"}";
ObjectMapper mapper = new ObjectMapper();
// Step 1: Read everything into one object.
Map<String, Object> all = mapper.readValue(jsonInString, Map.class);
// Step 2: Get your "normal" data into one object
Map<String, Object> someMap=(Map<String, Object>) all.get("someMap");
// Step 3: Get your "embedded" data from your object
String anotherMapStr = (String) all.get("anotherMap");
// Step 4: Deserialize embedded data
Map<String, Object> anotherMap = mapper.readValue(anotherMapStr, Map.class);
System.out.println(anotherMap);
System.out.println(someMap);
}
}
打印:
{100000000=360000, 100000048=172800, 100000036=129600, 100000024=86400, 100000012=43200}
{one_value=1, another_value=2}