I have a Jackson JsonNode of sub-type ObjectNode:
ObjectNode node = parent.path('somepath');
node has a number of sub-fields, such as you'd see in this json object:
{
"somepath": {
"a": 1,
"b": 2,
"c": 3,
"d": 4
}
}
So the above object node will have four sub-objects (all JsonNode/ObjectNodes in their own right): a, b, c and d.
Given object node, I'd like to filter out some of the subfields. For instance, let's say I'd like to filter out everything but some list of field names, say ["b", "c"]. When I re-serialize the node object it would look like this:
{
"somepath": {
"b": 2,
"c": 3
}
}
I can think of a lot of ways where I can loop through both the field name list and the keep list, and rebuild the object, but that all seems like a lot of work and very unclear. What I'd love to use is a Stream.filter() type of function:
List<String> keepList = Lists.newArrayList("b", "c");
node.stream().filter( field -> keepList.contains(field.name()));
Obviously the above code doesn't work because I can't 'stream' an ObjectNode. Is there a way I can get this to work in a similar fashion, or am I stuck going the long way around?
There is a method in ObjectNode that does exactly what you want: retain. You might use it this way:
ObjectNode node = parent.path('somepath');
node = node.retain(keepList);
Related
I'd like to get the requestedInstanceCount from instanceGroupName = slave. How can this be achieved with Jackson?
Below is the job-flow.json:
{
"generalId": "ABC"
"instanceCount": 4,
"instanceGroups": [
{
"instanceGroupId": "CDE",
"instanceGroupName": "master",
"requestedInstanceCount": 1
},
{
"instanceGroupId": "FGH",
"instanceGroupName": "slave",
"requestedInstanceCount": 8
}
]
}
So far this is what I have:
val jobFlowJson: String = new String(Files.readAllBytes(Paths.get("/mnt/var/lib/info/job-flow.json")))
val jsonNode = mapper.readValue(jobFlowJson, classOf[JsonNode])
val instanceCount = jsonNode.get("requestedInstanceCount").asInt
But there are 2 values and the order between master & slave can change at any time. Thanks in advance!
You have to go through the JSON tree step by step:
get the instanceGroups as an array
iterate over the array to find the item you want
extract the value requestedInstanceCount
Something like this (pseudo Scala code):
jsonNode.get("instance groups")
.asArray
.collect {
case item if item.get("instanceGroupName").asString == "..." =>
item.get("requestedInstanceCount")
}
Or define some case class representing the structure and pass on your JSON into the case class. It will be way easier to manipulate if you have no specific reason to not do this.
Using Jackson deserialization it would be great to have an option
to deserialize JSON array of items (of any nature: object, number, string) to a Java array of String.
I see that #JsonRawValue allows to have similar functionality for nested fields.
Is it possible to implement the same for "top level" objects?
void test(){
var payload = """ [
{"a": 1, "b": "hello"},
{"a": 2, "b": "bye"},
"something"
]
""";
// What else is required to get the effect of #JsonRawValue ?
String[] rawItems = new ObjectMapper().readValue(payload, String[].class);
assertEquals("""{"a": 1, "b": "hello"}""", rawItems[0]);
assertEquals("""{"a": 2, "b": "bye"}""", rawItems[1]);
assertEquals("\"something\"", rawItems[2]);
}
You can convert the payload to com.fasterxml.jackson.databind.node.ArrayNode and then iterate through elements (JsonNode in this case) and add them to a List<String> (which you can then convert to String[] if you want).
One thing to be aware of - the formatting will not be as you expect. JsonNode has two methods - toString() which deletes all the white space and toPrettyString which adds whitespaces and newlines to the final String
String payload = """
[
{"a": 1, "b": "hello"},
{"a": 2, "b": "bye"},
"something"
]
""";
ArrayNode items = new ObjectMapper().readValue(payload, ArrayNode.class);
List<String> rawItemsList = new ArrayList<>();
for (JsonNode jsonNode : items) {
rawItemsList.add(jsonNode.toString());
}
// You can just keep using the rawItemsList, but I've converted it to array since the question mentioned it
String[] rawItemsArr = rawItemsList.toArray(new String[0]);
assertEquals("""
{"a":1,"b":"hello"}""", rawItemsArr[0]);
assertEquals("""
{"a":2,"b":"bye"}""", rawItemsArr[1]);
assertEquals("\"something\"", rawItemsArr[2]);
I have following tree structure in Java (-> denotes parents of)
Node A -> Node B
Node B -> Node C
Node C -> Node D
Node C -> Node E
I have following class to represent the Tree
class TreeNode{
private String name;
private List<TreeNode> children;
}
And now when I serialise the data to Json, I wish to have the following output.
{
"A": {
"B": {
"C": {
"D": {},
"E": {}
}
}
}
}
I have managed to used custom serializer to remove the field names, however, the current response I receive has square brackets and I am not sure how to remove these.
Current output I get
{
"A": [
{
"B": [
{
"C": [
{
"D": []
},
{
"E": []
}
]
}
]
}
]
}
Serialiser I implemented
class TreeNodeSerializer extends JsonSerializer<TreeNode> {
#Override
public void serialize(Sample2Test.TreeNode value,
JsonGenerator gen,
SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeObjectField(value.getName(), value.getChildren());
gen.writeEndObject();
}
}
The core issue is that here
gen.writeObjectField(value.getName(), value.getChildren());
value.getChildren() is a List<Node>. So, the serializer serializes it as an Array, and then comes back to the custom serializer TreeNodeSerializer to serialize each children.
I don't quite understand how you get
{
"D": [],
"E": []
}
Since you only ever write one key to the object, so I expect it should be
{
"D": []
},
{
"E": []
}
instead.
To solve this, you need to actually serialize the children as well:
gen.writeFieldName(value.getName());
gen.writeStartObject();
for (Node child: value.getChildren()) {
gen.writeObjectField(child.getName(), child);
}
gen.writeEndObject();
However, you can now see that the name of the child is there twice, in the parent serialization and in it's own serialization.
So, you can remove it from itself, but you're left with the issue of solving for the root, meaning you'd need something like a root node that contains your node A.
Which makes sense, as nothing prevents your root object right now from having multiple keys in it, except your structure doesn't allow for it.
So, the final method should be:
gen.writeStartObject();
for (Node child: value.getChildren()) {
gen.writeObjectField(child.getName(), child);
}
gen.writeEndObject();
And create a dummy root node to hold your actual root(s) node(s)
Squara brackets in JSON (aka JavaScript) represent lists (aka arrays).
You didnt' provide the code for your serializator, but I'm guessing that you are simply adding children - which is a List.
Instead of a List, you shoud be putting a Map where keys are children's names and values are children's children. Of course, this will only work if your names are unique.
As it turns out Jackson does not do stable JSON object comparison contrary to this question. So I was wondering if GSON has stable comparison of JSON objects. (without having to override equals/implement one's own comparator)
Your gist shows org.json code, not Jackson.
Jackson has a perfectly able .equals() implementation for all JsonNodes. And that includes all "non container types" as well as "container types":
final JsonNodeFactory factory = JsonNodeFactory.instance;
final JsonNode node1 = factory.objectNode().put("hello", "world");
final JsonNode node2 = factory.objectNode().put("hello", "world");
node1.equals(node2); // true
Of course, it does respect JSON's "order does not matter" with object members: { "a": 1, "b": 2 } is equal to { "b": 2, "a": 1 } -- as it should.
That may only be my opinion as well, but really, when it comes to JSON, anything is better than org.json.
I have an JSONArray(org.json.JSONArray) of JSONObjects(org.json.JSONObject) like
[
{"id":"abc", "parent_id":""},
{"id":"def", "parent_id":"abc"},
{"id":"ghi", "parent_id":""},
{"id":"jkl", "parent_id":"abc"},
{"id":"mno", "parent_id":"ghi"},
{"id":"mno", "parent_id":"def"},
]
Here "id" field represents unique id of the Object and "parent_id" represents id of it's parent. I have to convert this JSONArray into another JSONArray where I can have elements nested inside their parent(directory like structure) like
[
{"id":"abc", "parent_id":"","children":[
{"id":"def", "parent_id":"abc","children":[
{"id":"mno", "parent_id":"def","children":[]}
]},
{"id":"jkl", "parent_id":"abc","children":[]}
]},
{"id":"ghi", "parent_id":"","children":[
{"id":"mno", "parent_id":"ghi","children":[]}
]},
]
Can anybody help me here what is the best possible way to do so?
You'll have something like this (pseudo code)
// Element is { id, children }
Dictionary<String, Element> elements;
for (JSONObject obj : arr) {
if (elements.hasKey(obj.id)) {
// Maybe you need to update your element or something here
} else {
// Create your element
elements[obj.id] = new Element(obj.id);
}
// if the parent does not exist, create a shadow of the parent
// (it'll get filled in with more info above if encountered later)
if (!elements.hasKey(obj.parent)) {
elements[obj.parent] = new Element(obj.parent);
}
// Add yourself to children
elements[obj.parent].children.push(elements[obj.id]);
}
// TODO: iterate your dictionary and put it into an array, this should be straightforward
// Or if you want the root of your tree return elements[""]
I apologize in advance for not being more specific, but this should work pretty generically for whatever you want to do. Also it's not Java, but easily convertible.