如何从 JsonParser (Jackson Json) 获取底层字符串
Posted
技术标签:
【中文标题】如何从 JsonParser (Jackson Json) 获取底层字符串【英文标题】:How to get the underlying String from a JsonParser (Jackson Json) 【发布时间】:2013-05-25 10:06:36 【问题描述】:查看文档和源代码,我没有看到一个明确的方法来做到这一点。很好奇我是否遗漏了什么。
假设我收到来自服务器响应的 InputStream。我从这个 InputStream 创建了一个 JsonParser。预计服务器响应是包含有效 JSON 的文本,例如:
"iamValidJson":"yay"
但是,如果响应最终是无效的 JSON 或根本不是 JSON,例如:
Some text that is not JSON
JsonParser 最终会抛出异常。在这种情况下,我希望能够从 JsonParser 中提取底层无效文本“Some text that is not JSON
”,以便将其用于其他目的。
我无法将其从 InputStream 中拉出,因为它不支持重置,并且 JsonParser 的创建会消耗它。
有没有办法做到这一点?
【问题讨论】:
请注意,您可以要求JsonParser
not 关闭底层输入流。
【参考方案1】:
如果您有JsonParser
,那么您可以使用jsonParser.readValueAsTree().toString()
。
但是,这可能要求被解析的 JSON 确实是有效的 JSON。
【讨论】:
我们在JsonDeserializer<Geometry>
中使用此方法来使用org.geotools.geojson.geom.GeometryJSON
解析器来解析GeoJSON
对象,因为它只是直接解析JSON String
。
“但是,这可能要求被解析的 JSON 确实是有效的 JSON”
如果您将 Content-Type
设置为 application/json
,那么您确实应该发送 JSON 格式的内容。也许您可以将其更改为 "textDescription": "text"
用“textDescription”替换您的特定情况下有意义的任何内容。
请注意,Jackson 可以解析任何 JSON 值,它不限于 JSON 文本(即,仅数组或对象)。事实上,大多数解析器都可以,尽管 RFC 理论上禁止这样做。
仅供参考,带引号的字符串或纯数字甚至常量值true
、false
和null
都是有效的 JSON。见JSON Values【参考方案2】:
我曾经使用自定义解串器,但我希望默认解串器完成大部分工作,然后使用 SAME json 做一些额外的自定义工作。但是,在默认反序列化器完成工作后,JsonParser 对象的当前位置超出了我需要的 json 文本。所以我遇到了和你一样的问题:如何访问底层的 json 字符串。
您可以使用JsonParser.getCurrentLocation.getSourceRef()
来访问底层的 json 源。使用JsonParser.getCurrentLocation().getCharOffset()
在json源中查找当前位置。
这是我使用的解决方案:
public class WalkStepDeserializer extends StdDeserializer<WalkStep> implements
ResolvableDeserializer
// constructor, logger, and ResolvableDeserializer methods not shown
@Override
public MyObj deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException
MyObj myObj = null;
JsonLocation startLocation = jp.getCurrentLocation();
long charOffsetStart = startLocation.getCharOffset();
try
myObj = (MyObj) defaultDeserializer.deserialize(jp, ctxt);
catch (UnrecognizedPropertyException e)
logger.info(e.getMessage());
JsonLocation endLocation = jp.getCurrentLocation();
long charOffsetEnd = endLocation.getCharOffset();
String jsonSubString = endLocation.getSourceRef().toString().substring((int)charOffsetStart - 1, (int)charOffsetEnd);
logger.info(strWalkStep);
// Special logic - use JsonLocation.getSourceRef() to get and use the entire Json
// string for further processing
return myObj;
关于在自定义解串器中使用默认解串器的信息位于How do I call the default deserializer from a custom deserializer in Jackson
【讨论】:
你从哪里得到defaultDeserializer
?
getSourceRef 如果解析器是使用输入流创建的,则返回 null【参考方案3】:
晚了 5 年,但这是我的解决方案:
我将 jsonParser 转换为字符串
String requestString = jsonParser.readValueAsTree().toString();
然后我将该字符串转换为 JsonParser
JsonFactory factory = new JsonFactory();
JsonParser parser = factory.createParser(requestString);
然后我遍历我的解析器
ObjectMapper objectMapper = new ObjectMapper();
while(!parser.isClosed())
JsonToken jsonToken = parser.nextToken();
if(JsonToken.FIELD_NAME.equals(jsonToken))
String currentName = parser.getCurrentName();
parser.nextToken();
switch (currentName)
case "someObject":
Object someObject = objectMapper.readValue(parser, Object.class)
//validate someObject
break;
我需要保存原始 json 字符串以用于日志记录,这就是我首先这样做的原因。很头疼,但终于做到了,我希望我能帮助别人:)
【讨论】:
这很有帮助。在我用ObjectMapper
注册的StdDeserializer
扩展中执行此操作,我必须执行此操作:final String result = jp.readValueAsTree().toString(); jp = OBJECT_MAPPER.getFactory().createParser(result); final JsonNode root = OBJECT_MAPPER.getFactory().getCodec().readTree(jp);
否则,编解码器在新工厂中为空。【参考方案4】:
构建我自己的反序列化器,我想在其中将特定字段反序列化为文本 i.s.o。一个合适的 DTO,这就是我想出的解决方案。
我这样写了自己的 JsonToStringDeserializer:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringEscapeUtils;
import java.io.IOException;
/**
* Deserialiser to deserialise any Json content to a String.
*/
@NoArgsConstructor
public class JsonToStringDeserializer extends JsonDeserializer<String>
/**
* Deserialise a Json attribute that is a fully fledged Json object, into a @link String.
* @param jsonParser Parsed used for reading JSON content
* @param context Context that can be used to access information about this deserialization activity.
* @return The deserialized value as a @link String.
* @throws IOException
*/
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException
final TreeNode node = jsonParser.getCodec().readTree(jsonParser);
final String unescapedString = StringEscapeUtils.unescapeJava(node.toString());
return unescapedString.substring(1, unescapedString.length()-1);
像这样注释要反序列化的字段:
@JsonDeserialize(using = JsonToStringDeserializer.class)
我最初遵循的建议是这样使用 TreeNode:
final TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser);
return treeNode.toString();
但是你会得到一个包含转义字符的 Json 字符串。
【讨论】:
【参考方案5】:您正在尝试做的事情超出了 Jackson 的范围(以及大多数,如果不是所有其他 Java JSON 库)。您要做的是将输入流完全消耗为字符串,然后尝试使用 Jackson 将该字符串转换为 JSON 对象。如果转换失败,则对中间字符串执行某些操作,否则正常进行。下面是一个例子,为了方便起见,它使用了优秀的Apache Commons IO library:
final InputStream stream ; // Your stream here
final String json = IOUtils.toString(stream);
try
final JsonNode node = new ObjectMapper().readTree(json);
// Do something with JSON object here
catch(final JsonProcessingException jpe)
// Do something with intermediate string here
【讨论】:
我相信你对它超出范围是正确的。我只是希望有其他解决方案,因为这样做会破坏使用流解析器的性能优势。但是,您的解决方案确实有效。 流解析器假定传入的流是有效的(它必须这样做,因为它在令牌推送模式下运行)。出于同样的性能原因,您提到它并没有“缓存”它已经处理的东西,它只是检查它是否有一个有效的令牌,如果有,它就会将它发送到链中的下一个处理程序。如果没有,它会发出嘶嘶声。另一种解决方案是实现一个累积流并将其链接到您使用的任何输入流。不一定干净,但它可以减轻您对流媒体性能的担忧。以上是关于如何从 JsonParser (Jackson Json) 获取底层字符串的主要内容,如果未能解决你的问题,请参考以下文章