I figured out that this behaviour can be achieved via configuration. Here is the kotlin code but it's simple to convert to java Just create xmlMapper with appropriate configuration
fun jacksonCreateXmlMapper(): XmlMapper {
val module = JacksonXmlModule()
module.setXMLTextElementName("value")
return XmlMapper(module)
}
For input
<products>
<product count="5">apple</product>
<product count="10">orange</product>
</products>
you get:
{
"product" : [ {
"count" : "5",
"value" : "apple"
}, {
"count" : "10",
"value" : "orange"
} ]
}
Answer from Aleksand Kondaurov on Stack OverflowHow to test if JSON Collection object is empty in Java - Stack Overflow
Why isEmpty( ) always returns true for jsonNode.get("some_key").isEmpty( )? Method isEmpty( ) works properly only if I add .asText( ) method: jsonNode.get("some_key").asText( ).isEmpty( );
Add `isEmpty()` implementation for `JsonNode` serializers
spring - How to check JsonNode value is empty or not : java - Stack Overflow
I figured out that this behaviour can be achieved via configuration. Here is the kotlin code but it's simple to convert to java Just create xmlMapper with appropriate configuration
fun jacksonCreateXmlMapper(): XmlMapper {
val module = JacksonXmlModule()
module.setXMLTextElementName("value")
return XmlMapper(module)
}
For input
<products>
<product count="5">apple</product>
<product count="10">orange</product>
</products>
you get:
{
"product" : [ {
"count" : "5",
"value" : "apple"
}, {
"count" : "10",
"value" : "orange"
} ]
}
You also could simply post-process the JSON DOM, traverse to all objects, and rename the keys that are empty strings to "value".
Race condition: such a key may already exist, and must not be overwritten
(e.g. <id type="pid" value="existing">abcdef123</id>).
Usage:
(note: you should not silently suppress the exception and return null, but allow it to propagate so the caller can decide to catch and apply failover logic if required)
public InputStream parseXmlResponse(InputStream xmlStream) throws IOException {
JsonNode node = xmlMapper.readTree(xmlStream);
postprocess(node);
return new ByteArrayInputStream(jsonMapper.writer().writeValueAsBytes(node));
}
Post-processing:
private void postprocess(JsonNode jsonNode) {
if (jsonNode.isArray()) {
ArrayNode array = (ArrayNode) jsonNode;
Iterable<JsonNode> elements = () -> array.elements();
// recursive post-processing
for (JsonNode element : elements) {
postprocess(element);
}
}
if (jsonNode.isObject()) {
ObjectNode object = (ObjectNode) jsonNode;
Iterable<String> fieldNames = () -> object.fieldNames();
// recursive post-processing
for (String fieldName : fieldNames) {
postprocess(object.get(fieldName));
}
// check if an attribute with empty string key exists, and rename it to 'value',
// unless there already exists another non-null attribute named 'value' which
// would be overwritten.
JsonNode emptyKeyValue = object.get("");
JsonNode existing = object.get("value");
if (emptyKeyValue != null) {
if (existing == null || existing.isNull()) {
object.set("value", emptyKeyValue);
object.remove("");
} else {
System.err.println("Skipping empty key value as a key named 'value' already exists.");
}
}
}
}
Output: just as expected.
{
"elementName": {
"id": {
"type": "pid",
"value": "abcdef123"
}
},
}
EDIT: considerations on performance:
I did a test with a large XML file (enwikiquote-20200520-pages-articles-multistream.xml, en.wikiquote XML dump, 498.4 MB), 100 rounds, with following measured times (using deltas with System.nanoTime()):
- average read time (File, SSD): 2870.96 ms
(JsonNode node = xmlMapper.readTree(xmlStream);) - average postprocessing time: 0.04 ms
(postprocess(node);) - average write time (memory): 0.31 ms
(new ByteArrayInputStream(jsonMapper.writer().writeValueAsBytes(node));)
That's a fraction of a millisecond for an object tree build from a ~500 MB file - so performance is excellent and no concern.
obj.length() == 0
is what I would do.
If you're okay with a hack -
obj.toString().equals("{}");
Serializing the object is expensive and moreso for large objects, but it's good to understand that JSON is transparent as a string, and therefore looking at the string representation is something you can always do to solve a problem.
It looks like we have two different isEmpty( ) methods. I'm using Jackson for JsonNode:
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.4</version></dependency>
To do a proper null check of a JsonNode field:
JsonNode jsonNode = response.get("item");
if(jsonNode == null || jsonNode.isNull()) { }
The item is either not present in response, or explicitly set to null .
OK, so if the node always exists, you can check for null using the .isNull() method.
Copyif (!response.isNull("item")) {
// do some things with the item node
} else {
// do something else
}