Davyzhu Davyzhu - 3 months ago 13
Java Question

How to extract part of the *original* text from JSON?

PLEASE, don't edit the question for me. My question is on String manipulation, changing the flow of text can very likely change the meaning of the question and cause confusion.

The problem can be viewed as a String manipulation problem. But I expect a solution in Jackson to solve my problem.

Suppose I have received a String

{"payload":{"foo":"bar","ipsum":["lorem","lorem"]},"signature":"somehmacsign"}
. When it's displayed, it's like:

{
"payload": {
"foo": "bar",
"ipsum": [
"lorem",
"lorem"
]
},
"signature": "somehmacsign"
}


How can I extract its substring from 11th character
{
till the
}
just before
,"signature"
. Namely,
{"foo":"bar","ipsum":["lorem","lorem"]}
. Semantically, I want to extract the original string representation for the
payload
field. I suppose it should not involve parsing the JSON string to Java objects and back to String. I don't want to risk losing the order of fields, spacing, or whatever small features because it's meant to be hmac signed.

Thanks in advance!

EDIT1: Rephrased to clarify that this problem has nothing to do with Java String literal.

EDIT2: Though it may be a bit early to say, there is no obvious off-the-shelf solution to my question in general (how to partial extract/parse a JSON string). In fact, I myself find my lazy/partial parsing theory a bit verbose. It requires way too many of passes to locate a deeply-nested node.

In particular for my situation, I found appending the signature in the body of a request a bad idea as it poses difficulties for the receiving party. I'm considering moving the signature to HTTP header, maybe
X-Auth-Token
?

Thanks for all that participated!

EDIT 3: As it turns out, I was really concluding too early. Cassio's answer perfectly solves the problem by using custom
Deserializer
and the magic
skipChildren
!

Answer

You could move the signature to a HTTP header to make things simpler.

However, if you want to keep the signature in the request payload, follow the steps described below.

Creating a custom deserializer

Create a custom deserializer that can get the raw JSON string value:

public class RawJsonDeserializer extends JsonDeserializer<String> {

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

        long begin = jp.getCurrentLocation().getCharOffset();
        jp.skipChildren();
        long end = jp.getCurrentLocation().getCharOffset();

        String json = jp.getCurrentLocation().getSourceRef().toString();
        return json.substring((int) begin - 1, (int) end);
    }
}

Creating a Java class to hold the values

Create a Java class to hold the values (pay attention to the @JsonDeserialize annotation):

public class Request {

    @JsonDeserialize(using = RawJsonDeserializer.class)
    private String payload;

    private String signature;

    // Getters and setters ommitted
}

Parsing the JSON

Parse the JSON using ObjectMapper and Jackson will use the deserializer you have defined above:

String json = "{\"payload\":{\"foo\":\"bar\",\"ipsum\":[\"lorem\"," +
              "\"lorem\"]},\"signature\":\"somehmacsign\"}";

ObjectMapper mapper = new ObjectMapper();
Request request = mapper.readValue(json, Request.class);

If the payload is an invalid JSON, Jackson will throw an exception.