2

I am looking for a way to include only certain fields from the JSON request received to my service and then log them accordingly to avoid logging too much data.

The fields to include and hence log would be configured in a properties file.

Since I use Spring Boot for my service, Jackson JARs should already be available.

Since the request JSON is complex with nested arrays and objects as fields am not sure if I can achieve my requirement with Jackson.

Is there a way to extract only certain fields along with their values from a input request JSON using the Jackson API?

Basically, I am looking at 2 use cases.

1.Select one or more elements (which I intend to pass by config) from Json String and then render them

2.Select and update one or more elements inline. I want to mask the values of the elements before rendering them.

I am providing the code for selecting the element and Json which I used along with what I expect as below.

public String getValues(String key,String jsonString){
    String fieldNodeStr =null;
    try {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node = mapper.readTree(jsonString);
        JsonNode fieldNode = node.at(key);
        fieldNodeStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(fieldNode);
        System.out.println(fieldNodeStr);
    } catch (IOException e) {
        System.out.println("IOException:",e);
    }catch (Exception e) {
        System.out.println("Exception:",e);
    }
}   

My Json is as below,

{
    "employeeId" : "5353",
    "salesId" : "sales005",
    "manager" : {
        "userId" : "managerUser",
        "isSuperUser" : false,
        "firstName":"manager first name",
        "lastName":"manager last name"
            },
    "administrator" : {
        "userId" : "administratorUser",
        "isSuperUser" : false,
        "firstName":"admin first name",
        "lastName":"admin last name"
    },
    "requester" : [
                    {
                            "id":"1234",
                            "demographic" : {
                                                "firstName" : "hello",
                                                "lastName" : "hai"
                                             }
                     },
                     {
                            "id":"1235",
                            "demographic" : {
                                                "firstName" : "welcome",
                                                "lastName" : "user"
                                            }
                      }
                   ]

}

I have 2 issues as below.

If I pass "/manager/userId" ,"/administrator/isSuperUser" (OR) "/salesId" I am able to get the expected value.

But, If want to get the /requester/id (OR) /requester/demographic (OR) /requester/demographic/lastName , I am not able to fetch. I am getting null values.

I expect the following when I pass , "/requester/id" (OR) "/requester/demographic" respectively.

"requester" : [
                    {
                            "id":"1234"
                     },
                     {
                            "id":"1235"
                      }
                ]


 "requester" : [
                    {
                            "demographic" : {
                                                "firstName" : "hello",
                                                "lastName" : "hai"
                                             }
                     },
                     {
                            "demographic" : {
                                                "firstName" : "welcome",
                                                "lastName" : "user"
                                            }
                      }
                   ]

Along with fetch I also want to update the values inline after locating them with JsonPointer

I have my code as below for the updation,

public String findAndUpdate(String key,String jsonString,String repValue){
    String fieldNodeStr =null;
    try {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node = mapper.readTree(jsonString);
        JsonNode fieldNode = node.at(key);

        //Update the value of located node to a different one
        ((ObjectNode) fieldNode).put(key,repValue);

        fieldNodeStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(fieldNode);
        System.out.println(fieldNodeStr);
    } catch (IOException e) {
        System.out.println("IOException:",e);
    }catch (Exception e) {
        System.out.println("Exception:",e);
    }
    return fieldNodeStr;
}

When I pass, "/manager/userId" as value of key, I get the below error,

17:21:24.829 [main] ERROR com.demo.jsondemo.TestClass - Exception:
java.lang.ClassCastException: com.fasterxml.jackson.databind.node.TextNode cannot be cast to com.fasterxml.jackson.databind.node.ObjectNode
    at com.demo.jsondemo.TestClass.findAndUpdate(TestClass.java:95) [classes/:na]
    at com.demo.jsondemo.TestClass.main(TestClass.java:221) [classes/:na]

2 Answers 2

9

JSON Pointer

You could use JSON Pointer (a string syntax for identifying a specific value within a JSON document) defined by the RFC 6901.

For example purposes, consider the following JSON:

{
  "firstName": "John",
  "lastName": "Doe",
  "address": {
    "street": "21 2nd Street",
    "city": "New York",
    "postalCode": "10021-3100",
    "coordinates": {
      "latitude": 40.7250387,
      "longitude": -73.9932568
    }
  }
}

To query the coordinates node, you could use the following JSON Pointer expression:

/address/coordinates

JSON Pointer and Jackson

Jackson 2.3.0 introduced support to JSON Pointer and it can be used as following:

String json = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"address\":{\"street\":"
        + "\"21 2nd Street\",\"city\":\"New York\",\"postalCode\":\"10021-3100\","
        + "\"coordinates\":{\"latitude\":40.7250387,\"longitude\":-73.9932568}}}";

ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(json);
JsonNode coordinatesNode = node.at("/address/coordinates");

The coordinates node could be parsed into a bean:

Coordinates coordinates = mapper.treeToValue(coordinatesNode, Coordinates.class);

Or can be written as String:

String coordinatesNodeAsString = mapper.writerWithDefaultPrettyPrinter()
                                       .writeValueAsString(coordinatesNode);

Jayway JsonPath

A good alternative to JSON Pointer is JSONPath. In Java, there's an implementation called Jayway JsonPath, which integrates with Jackson.

To query the coordinates node with JsonPath, you would use the following expression:

$.address.coordinates

And the code to query the node would be like:

JsonNode coordinatesNode = JsonPath.parse(json)
                                   .read("$.address.coordinates", JsonNode.class);

JsonPath requires the following dependency:

<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <version>2.2.0</version>
</dependency>

And, to integrate with Jackson, following lines are required:

Configuration.setDefaults(new Configuration.Defaults() {

    private final JsonProvider jsonProvider = new JacksonJsonProvider();
    private final MappingProvider mappingProvider = new JacksonMappingProvider();

    @Override
    public JsonProvider jsonProvider() {
        return jsonProvider;
    }

    @Override
    public MappingProvider mappingProvider() {
        return mappingProvider;
    }

    @Override
    public Set<Option> options() {
        return EnumSet.noneOf(Option.class);
    }
});

Update based on your requirements

Based on the additional details you have provided in your question, I would say JSONPath will offer you more flexibility the JSON Pointer.

You can try the following:

  • Instead of /requester/id, use $.requester[*].id.
  • Instead of /requester/demographic, use $.requester[*].demographic.

These expressions can be tested online. Have a look at the following resources:

And read the Jayway JsonPath documentation to understand how to use it properly.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your response. I tried the Jackson's JSON Pointer approach. I have some difficulty in fetching elements which are arrays and also updating them. I am not able to update them as I get java.lang.ClassCastException: com.fasterxml.jackson.databind.node.TextNode cannot be cast to com.fasterxml.jackson.databind.node.ObjectNode I have shared the code in github.com/bsridhar77/jsondemo/blob/master/src/main/java/com/…. Can you please help me.
@sri77 I got no errors when running your code. By the way, edit your question adding the relevant parts of your code.
I have added code and json I used. This I hope can give a better idea of my problem.
Thanks. I think JSON Path might be better. But, I am not sure if there is any way to update once I have located the field in Jayway JsonPath. As in my second scenario, I want to find and also update inline.
@sri77 You can use ObjectNode from Jackson to create/modify. If you have a JsonNode, you can cast it into a ObjectNode and use the setter methods. To understand the difference between JsonNode and ObjectNode, refer to this answer.
0

Jackson's JsonView annotations should allow you to mark certain fields/getters with a particular view. Something like the following should work.

public class Item {
    public static class LoggingView {}

    @JsonView(LoggingView.class)
    public int id;

    @JsonView(LoggingView.class)
    public String name;

    public byte[] data;
}

This should allow you to write id and name without writing data by doing the following:

public class Something {
    private static final Logger logger = LoggerFactory.getLogger();
    private static final ObjectWriter loggingItemWriter = new ObjectMapper().writerWithView(Item.LoggingView.class);

    public void doSomething(Item item) {
        ...
        logger.info(loggingItemWriter.writeValueAsString(item));
        ...
    }
}

1 Comment

Thanks for your response. I understand JsonView can help in creating custom views based on one or more fields in the POJO. But, what I want is a generic solution agnostic of POJO. Basically, whenever I receive a JSON Request , which can be for any service, I want to have the capabiity to filter out certain fields which can be configured per service even before the request gets bound to the POJO. I am looking to use the logic in a Filter which will filter out and log the configured fields accordingly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.