使用 Jackson 从 JSON 获取单个字段 [英] Get single field from JSON using Jackson

查看:110
本文介绍了使用 Jackson 从 JSON 获取单个字段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给定一个任意的 JSON,我想获取单个字段 contentType 的值.Jackson 如何做到这一点?

<代码>{内容类型:富",fooField1: ...}{内容类型:酒吧",条形阵列:[...]}

相关

解决方案

The Jackson Way

考虑到您没有描述数据结构的 POJO,您可以简单地执行以下操作:

final String json = "{"contentType": "foo", "fooField1": ... }";最终 ObjectNode 节点 = new ObjectMapper().readValue(json, ObjectNode.class);//^//实际上,尝试*重用* ObjectMapper 的单个实例如果(节点.有(内容类型")){System.out.println("contentType:" + node.get("contentType"));}

<小时>

在评论部分解决问题

但是,如果您不希望使用整个源 String,而只想访问您知道其路径的特定属性,则您必须自己编写,利用 Tokeniser.><小时>

实际上,这是周末,我有时间,所以我可以给你一个良好的开端:这是一个基本的!它可以在 strict 模式下运行并发出合理的错误消息,或者在请求无法满足时放宽并返回 Optional.empty.

公共静态类 JSONPath {受保护的静态最终 JsonFactory JSON_FACTORY = new JsonFactory();私有最终列表<JSONKey>钥匙;公共 JSONPath(最终字符串来自){this.keys = Arrays.stream((from.startsWith("[") ? from : String.valueOf("." + from)).split("(?=\[|\]|\.)")).filter(x -> !"]".equals(x)).map(JSONKey::new).collect(Collectors.toList());}公共可选<字符串>getWithin(final String json) 抛出 IOException {返回 this.getWithin(json, false);}公共可选<字符串>getWithin(final String json, final boolean strict) 抛出 IOException {尝试(最终 InputStream 流 = 新 StringInputStream(json)){返回 this.getWithin(stream,strict);}}公共可选<字符串>getWithin(final InputStream json) 抛出 IOException {返回 this.getWithin(json, false);}公共可选<字符串>getWithin(final InputStream json, final boolean strict) 抛出 IOException {返回 getValueAt(JSON_FACTORY.createParser(json), 0, strict);}受保护的可选<字符串>getValueAt(final JsonParser parser, final int idx, final boolean strict) 抛出 IOException {尝试 {如果(解析器.isClosed()){返回 Optional.empty();}if (idx >= this.keys.size()) {parser.nextToken();if (null == parser.getValueAsString()) {throw new JSONPathException("选中的节点不是叶子");}返回 Optional.of(parser.getValueAsString());}this.keys.get(idx).advanceCursor(parser);返回 getValueAt(解析器,idx + 1,严格);} catch (final JSONPathException e) {如果(严格){throw (null == e.getCause() ? new JSONPathException(e.getMessage() + String.format(", at path: '%s'", this.toString(idx)), e) : e);}返回 Optional.empty();}}@覆盖公共字符串 toString() {return ((Function) x -> x.startsWith(".") ? x.substring(1) : x).apply(this.keys.stream().map(JSONKey::toString).collect(Collectors.joining()));}私有字符串 toString(final int idx) {return ((Function) x -> x.startsWith(".") ? x.substring(1) : x).apply(this.keys.subList(0, idx).stream().map(JSONKey::toString).collect(Collectors.joining()));}@SuppressWarnings("串行")公共静态类 JSONPathException 扩展 RuntimeException {公共 JSONPathException() {极好的();}公共 JSONPathException(最终字符串消息){超级(消息);}公共 JSONPathException(最终字符串消息,最终抛出原因){超级(消息,原因);}公共 JSONPathException(最终的 Throwable 原因){超级(原因);}}私有静态类 JSONKey {私人最终字符串键;私有最终 JsonToken startToken;公共 JSONKey(最终字符串 str){this(str.substring(1), str.startsWith("[") ? JsonToken.START_ARRAY : JsonToken.START_OBJECT);}私有 JSONKey(最终字符串键,最终 JsonToken startToken){this.key = 键;this.startToken = startToken;}/*** 前进光标直到找到当前的 {@link JSONKey},或* 消耗了当前 JSON 对象或数组的全部内容.*/public void AdvanceCursor(final JsonParser parser) 抛出 IOException {最后的 JsonToken token = parser.nextToken();如果 (!this.startToken.equals(token)) {throw new JSONPathException(String.format("'%s' 类型的预期令牌,得到:'%s'", this.startToken, token));}如果 (JsonToken.START_ARRAY.equals(this.startToken)) {//在 JSON 数组中移动光标for (int i = 0; i != Integer.valueOf(this.key).intValue(); i++) {JSONKey.skipToNext(解析器);}} 别的 {//在 JSON 对象中移动光标字符串名称;for (parser.nextToken(), name = parser.getCurrentName(); !this.key.equals(name); parser.nextToken(), name = parser.getCurrentName()) {JSONKey.skipToNext(解析器);}}}/*** 将光标移动到当前 JSON 对象中的下一个条目* 或数组.*/private static void skipToNext(final JsonParser parser) 抛出 IOException {最后的 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("找不到请求的密钥");}}/*** 递归地消耗下一个直到回到*相同的深度级别.*/private static void skipToNextImpl(final JsonParser parser, final int depth) 抛出 IOException {如果(深度== 0){返回;}最后的 JsonToken token = parser.nextToken();if (JsonToken.START_ARRAY.equals(token) || JsonToken.START_OBJECT.equals(token) || JsonToken.FIELD_NAME.equals(token)) {skipToNextImpl(解析器,深度+ 1);} 别的 {skipToNextImpl(解析器,深度 - 1);}}@覆盖公共字符串 toString() {return String.format(this.startToken.equals(JsonToken.START_ARRAY) ? "[%s]" : ".%s", this.key);}}}

假设以下 JSON 内容:

<代码>{人们": [{"name": "埃里克",年龄":28}, {"name": "卡琳",年龄":26}],公司": {"name": "榆树农场","address": "3756 Preston Street Wichita, KS 67213",电话":857-778-1265"}}

...你可以使用我的 JSONPath 类,如下所示:

 final String json = "{"people":[],"company":{}}";//参考上面的JSONSystem.out.println(new JSONPath("people[0].name").getWithin(json));//可选[埃里克]System.out.println(new JSONPath("people[1].name").getWithin(json));//可选[卡琳]System.out.println(new JSONPath("people[2].name").getWithin(json));//可选.空System.out.println(new JSONPath("people[0].age").getWithin(json));//可选[28]System.out.println(new JSONPath("company").getWithin(json));//可选.空System.out.println(new JSONPath("company.name").getWithin(json));//可选[榆树农场]

请记住,它是基本的.它不强制数据类型(它返回的每个值都是一个 String)并且只返回叶节点.

实际测试用例

它处理InputStreams,因此您可以针对一些巨大的JSON文档对其进行测试,并发现它比浏览器下载和显示其内容的速度要快得多:

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

快速测试

注意我没有重复使用任何已经存在的 JSONPathObjectMapper 所以结果不准确——这只是一个非常粗略的比较:

public static Long time(final Callabler) throws Exception {最后长开始 = System.currentTimeMillis();r.call();返回 Long.valueOf(System.currentTimeMillis() - start);}public static void main(final String[] args) 抛出异常 {最终 URL url = 新 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 否则只下载整个文档",time(() -> new Scanner(url.openStream()).useDelimiter("\A").next())));System.out.println(String.format( "%dms 直截了当地将它完全映射到 Jackson 并访问特定字段",time(() -> new ObjectMapper().readValue(url.openStream(), ObjectNode.class).get("info").get("contact").get("email"))));}

<块引用>

378 毫秒使用 JSONPath 获取info.contact.email"
否则只下载整个文档需要 756 毫秒
896ms 直截了当地将其与 Jackson 完全映射并访问特定字段

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

解决方案

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

这篇关于使用 Jackson 从 JSON 获取单个字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆