Get single field from JSON using Jackson

╄→尐↘猪︶ㄣ 提交于 2019-11-27 03:14:18

问题


Given an arbitrary JSON I would like to get value of a single field contentType. How to do it with Jackson?

{
  contentType: "foo",
  fooField1: ...
}

{
  contentType: "bar",
  barArray: [...]
}

Related

  • How to find specified name and its value in JSON-string from Java? (GSON)
  • Using gson to deserialize specific JSON field of an object (GSON)

回答1:


The Jackson Way

Considering that you don't have a POJO describing your data structure, you could simply do:

final String json = "{\"contentType\": \"foo\", \"fooField1\": ... }";
final ObjectNode node = new ObjectMapper().readValue(json, ObjectNode.class);
//                              ^ 
// actually, try and *reuse* a single instance of ObjectMapper

if (node.has("contentType")) {
    System.out.println("contentType: " + node.get("contentType"));
}    

Addressing concerns in the comments section

If, however, you wish to not consume the entire source String, but simply access a specific property whose path you know, you'll have to write it yourself, leveraging a Tokeniser.


Actually, it's the weekend and I got time on my hands, so I could give you a head start: here's a basic one! It can run in strict mode and spew out sensible error messages, or be lenient and return Optional.empty when the request couldn't be fulfilled.

public static class JSONPath {

    protected static final JsonFactory JSON_FACTORY = new JsonFactory();

    private final List<JSONKey> keys;

    public JSONPath(final String from) {
        this.keys = Arrays.stream((from.startsWith("[") ? from : String.valueOf("." + from))
                .split("(?=\\[|\\]|\\.)"))
                .filter(x -> !"]".equals(x))
                .map(JSONKey::new)
                .collect(Collectors.toList());
    }

    public Optional<String> getWithin(final String json) throws IOException {
        return this.getWithin(json, false);
    }

    public Optional<String> getWithin(final String json, final boolean strict) throws IOException {
        try (final InputStream stream = new StringInputStream(json)) {
            return this.getWithin(stream, strict);
        }
    }

    public Optional<String> getWithin(final InputStream json) throws IOException {
        return this.getWithin(json, false);
    }

    public Optional<String> getWithin(final InputStream json, final boolean strict) throws IOException {
        return getValueAt(JSON_FACTORY.createParser(json), 0, strict);
    }

    protected Optional<String> getValueAt(final JsonParser parser, final int idx, final boolean strict) throws IOException {
        try {
            if (parser.isClosed()) {
                return Optional.empty();
            }

            if (idx >= this.keys.size()) {
                parser.nextToken();
                if (null == parser.getValueAsString()) {
                    throw new JSONPathException("The selected node is not a leaf");
                }

                return Optional.of(parser.getValueAsString());
            }

            this.keys.get(idx).advanceCursor(parser);
            return getValueAt(parser, idx + 1, strict);
        } catch (final JSONPathException e) {
            if (strict) {
                throw (null == e.getCause() ? new JSONPathException(e.getMessage() + String.format(", at path: '%s'", this.toString(idx)), e) : e);
            }

            return Optional.empty();
        }
    }

    @Override
    public String toString() {
        return ((Function<String, String>) x -> x.startsWith(".") ? x.substring(1) : x)
                .apply(this.keys.stream().map(JSONKey::toString).collect(Collectors.joining()));
    }

    private String toString(final int idx) {
        return ((Function<String, String>) x -> x.startsWith(".") ? x.substring(1) : x)
                .apply(this.keys.subList(0, idx).stream().map(JSONKey::toString).collect(Collectors.joining()));
    }

    @SuppressWarnings("serial")
    public static class JSONPathException extends RuntimeException {

        public JSONPathException() {
            super();
        }

        public JSONPathException(final String message) {
            super(message);
        }

        public JSONPathException(final String message, final Throwable cause) {
            super(message, cause);
        }

        public JSONPathException(final Throwable cause) {
            super(cause);
        }
    }

    private static class JSONKey {

        private final String key;
        private final JsonToken startToken;

        public JSONKey(final String str) {
            this(str.substring(1), str.startsWith("[") ? JsonToken.START_ARRAY : JsonToken.START_OBJECT);
        }

        private JSONKey(final String key, final JsonToken startToken) {
            this.key = key;
            this.startToken = startToken;
        }

        /**
         * Advances the cursor until finding the current {@link JSONKey}, or
         * having consumed the entirety of the current JSON Object or Array.
         */
        public void advanceCursor(final JsonParser parser) throws IOException {
            final JsonToken token = parser.nextToken();
            if (!this.startToken.equals(token)) {
                throw new JSONPathException(String.format("Expected token of type '%s', got: '%s'", this.startToken, token));
            }

            if (JsonToken.START_ARRAY.equals(this.startToken)) {
                // Moving cursor within a JSON Array
                for (int i = 0; i != Integer.valueOf(this.key).intValue(); i++) {
                    JSONKey.skipToNext(parser);
                }
            } else {
                // Moving cursor in a JSON Object
                String name;
                for (parser.nextToken(), name = parser.getCurrentName(); !this.key.equals(name); parser.nextToken(), name = parser.getCurrentName()) {
                    JSONKey.skipToNext(parser);
                }
            }
        }

        /**
         * Advances the cursor to the next entry in the current JSON Object
         * or Array.
         */
        private static void skipToNext(final JsonParser parser) throws IOException {
            final JsonToken token = parser.nextToken();
            if (JsonToken.START_ARRAY.equals(token) || JsonToken.START_OBJECT.equals(token) || JsonToken.FIELD_NAME.equals(token)) {
                skipToNextImpl(parser, 1);
            } else if (JsonToken.END_ARRAY.equals(token) || JsonToken.END_OBJECT.equals(token)) {
                throw new JSONPathException("Could not find requested key");
            }
        }

        /**
         * Recursively consumes whatever is next until getting back to the
         * same depth level.
         */
        private static void skipToNextImpl(final JsonParser parser, final int depth) throws IOException {
            if (depth == 0) {
                return;
            }

            final JsonToken token = parser.nextToken();
            if (JsonToken.START_ARRAY.equals(token) || JsonToken.START_OBJECT.equals(token) || JsonToken.FIELD_NAME.equals(token)) {
                skipToNextImpl(parser, depth + 1);
            } else {
                skipToNextImpl(parser, depth - 1);
            }
        }

        @Override
        public String toString() {
            return String.format(this.startToken.equals(JsonToken.START_ARRAY) ? "[%s]" : ".%s", this.key);
        }
    }
}

Assuming the following JSON content:

{
  "people": [{
    "name": "Eric",
    "age": 28
  }, {
    "name": "Karin",
    "age": 26
  }],
  "company": {
    "name": "Elm Farm",
    "address": "3756 Preston Street Wichita, KS 67213",
    "phone": "857-778-1265"
  }
}

... you could use my JSONPath class as follows:

    final String json = "{\"people\":[],\"company\":{}}"; // refer to JSON above
    System.out.println(new JSONPath("people[0].name").getWithin(json)); // Optional[Eric]
    System.out.println(new JSONPath("people[1].name").getWithin(json)); // Optional[Karin]
    System.out.println(new JSONPath("people[2].name").getWithin(json)); // Optional.empty
    System.out.println(new JSONPath("people[0].age").getWithin(json));  // Optional[28]
    System.out.println(new JSONPath("company").getWithin(json));        // Optional.empty
    System.out.println(new JSONPath("company.name").getWithin(json));   // Optional[Elm Farm]

Keep in mind that it's basic. It doesn't coerce data types (every value it returns is a String) and only returns leaf nodes.

Actual test case

It handles InputStreams, so you can test it against some giant JSON document and see that it's much faster than it would take your browser to download and display its contents:

System.out.println(new JSONPath("info.contact.email")
            .getWithin(new URL("http://test-api.rescuegroups.org/v5/public/swagger.php").openStream()));
// Optional[support@rescuegroups.org]

Quick test

Note I'm not re-using any already existing JSONPath or ObjectMapper so the results are inaccurate -- this is just a very rough comparison anyways:

public static Long time(final Callable<?> r) throws Exception {
    final long start = System.currentTimeMillis();
    r.call();
    return Long.valueOf(System.currentTimeMillis() - start);
}

public static void main(final String[] args) throws Exception {
    final URL url = new URL("http://test-api.rescuegroups.org/v5/public/swagger.php");
    System.out.println(String.format(   "%dms to get 'info.contact.email' with JSONPath",
                                        time(() -> new JSONPath("info.contact.email").getWithin(url.openStream()))));
    System.out.println(String.format(   "%dms to just download the entire document otherwise",
                                        time(() -> new Scanner(url.openStream()).useDelimiter("\\A").next())));
    System.out.println(String.format(   "%dms to bluntly map it entirely with Jackson and access a specific field",
                                        time(() -> new ObjectMapper()
                                                .readValue(url.openStream(), ObjectNode.class)
                                                .get("info").get("contact").get("email"))));
}

378ms to get 'info.contact.email' with JSONPath
756ms to just download the entire document otherwise
896ms to bluntly map it entirely with Jackson and access a specific field




回答2:


If you are using JSON jars in your application then the following code snippet is useful:

String json = "{\"contentType\": \"foo\", \"fooField1\": ... }";
JSONObject jsonObject = new JSONObject(json);
System.out.println(jsonObject.getString("contentType"));

and if you are using Gson jars then the same code will look like following:

Gson gson = new GsonBuilder().create();
Map jsonMap = gson.fromJson(json, Map.class);
System.out.println(jsonMap.get("contentType"));



回答3:


Just want to update for 2019. I found the following easiest to impl:

//json can be file or String
JsonNode parent= new ObjectMapper().readTree(json);
String content = parent.path("contentType").asText();

I would suggest to use path instead of get as get throws a NPE, where path returns with a default 0 or "", which is safer to work with if setting up the parsing correctly for 1st time.

My $0.02




回答4:


Another way is:

String json = "{\"contentType\": \"foo\", \"fooField1\": ... }";
JsonNode parent= new ObjectMapper().readTree(json);
String content = parent.get("contentType").asText();



回答5:


my Problem is a bit tricky.. for below Object i need JSON String ..

Below is my class in which there are some String and one JSON object we need to persist in DB in a single table ..

public class TestTransaction extends TestTransaction {
private String radarId;
private String exclusionReason;
private Timestamp eventTs;
private JSONObject drivingAttributes;

I am using an Object mapper to convert this object but it is giving me a Json inside Json.. that is wrong.. i need to convert inside JSON object to String using below mapper

public static final ObjectMapper EXCLUSION_OBJECT_MAPPER = new ObjectMapper()
        .setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'+0000'"))
        .setSerializationInclusion(JsonInclude.Include.NON_NULL);

invalid JSON is:

    {
    "txnId": "Test1",
    "txnIdDomain": "TEST1",
    "tradeDate": "2019-06-13T08:59:33.000+0000",
    "txnVersion": 0,
    "txnRevision": 0,
    "radarId": "TEST2",
    "exclusionReason": "CurrencyFilter Exclusion reason",
    "eventTs": "2019-07-18T19:03:32.426+0000",
    "drivingAttributes": {
        "Contra currency id": "USD",
        "Dealt currency id": "CAD"
    },
    "validated": false
}

correct JSON shall be

{
    "txnId": "Test1",
    "txnIdDomain": "TEST1",
    "tradeDate": "2019-06-13T08:59:33.000+0000",
    "txnVersion": 0,
    "txnRevision": 0,
    "radarId": "TEST2",
    "exclusionReason": "CurrencyFilter Exclusion reason",
    "eventTs": "2019-07-18T19:03:32.426+0000",
    "drivingAttributes": "Contra currency id:USD, Dealt currency id:CAD",
    "validated": false
}


来源:https://stackoverflow.com/questions/26190851/get-single-field-from-json-using-jackson

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!