Java: find JSON object attribute as exact JSON substring (with exact formatting)

I need to extract a field from a JSON object as a substring with exactly the same formatting and all whitespaces preserved.

For example when I have this object:

{
    "body": 
     {
        "name": "John",
        "surname": "Smith"
     },
    "signature": "message_signature"
}

I need to extract the field body as an exact sub-string.

So the result should look like this:

    {
        "name": "John",
        "surname": "Smith"
    }

I cannot use a standard JSON library such as Jackson, because it parses the JSON into a tree structure and the formatting is lost. I need some library which would be able to return the original sub-string for any given node.

The reason why I need this is to verify the message signature, which takes into account all the whitespaces (it’s a 3rd-party API, can’t do anything about that).

Answer

You can achieve this with Jackson by writing a custom deserializer for your body property. In the deserializer you would then extract the value raw between the starting “{” and ending “}”.

See a showcase here (ignoring proper error/edge case handling):

public class App {
    public static void main(String[] args) throws JsonMappingException, JsonProcessingException {
        var json = """
                    {
                            "body":
                             {
                                "name": "John",
                                "surname": "Smith"
                             },
                            "signature": "message_signature"
                        }
                """;

        var om = new ObjectMapper();

        System.out.println(om.readValue(json, Data.class));
    }

    public static class BodyDeserializer extends JsonDeserializer<String> {

        @Override
        public String deserialize(JsonParser jp, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {

            var startBody = (int) jp.getCurrentLocation().getCharOffset();
            jp.skipChildren();
            var endBody = (int) jp.getCurrentLocation().getCharOffset();

            var rawJson = jp.getCurrentLocation().getSourceRef().toString();

            return rawJson.substring(startBody - 1, endBody);
        }
    }

    public static class Data {

        @JsonProperty("body")
        @JsonDeserialize(using = BodyDeserializer.class)
        public String body;

        @JsonProperty("signature")
        public String signature;

        @Override
        public String toString() {
            return "Data [body=" + body + ", signature=" + signature + "]";
        }

    }

}