Вопрос:

Получить одно поле из JSON, используя Джексона

java json jackson

30284 просмотра

4 ответа

13670 Репутация автора

Учитывая произвольный JSON, я хотел бы получить значение одного поля contentType. Как это сделать с Джексоном ?

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

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

связанные с

Автор: Jakub M. Источник Размещён: 04.10.2014 07:56

Ответы (4)


31 плюса

6084 Репутация автора

Решение

Путь Джексона

Учитывая, что у вас нет POJO, описывающего вашу структуру данных, вы можете просто сделать:

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"));
}    

Решение проблем в разделе комментариев

Однако, если вы хотите не использовать весь источник String, а просто получить доступ к определенному свойству, путь которого вы знаете, вам придется написать его самостоятельно, используя Tokeniser.


На самом деле, это выходные, и у меня есть время, чтобы я мог дать вам преимущество: вот основной! Он может работать в strictрежиме и выдавать разумные сообщения об ошибках или быть снисходительным и возвращаться, Optional.emptyкогда запрос не может быть выполнен.

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);
        }
    }
}

Предполагая следующее содержимое JSON:

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

... вы можете использовать мой JSONPathкласс следующим образом:

    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]

Имейте в виду, что это основное . Он не приводит типы данных (каждое возвращаемое значение равно a String) и возвращает только конечные узлы.

Актуальный тестовый кейс

Он обрабатывает InputStreams, так что вы можете проверить его на каком-то гигантском документе JSON и увидеть, что он намного быстрее, чем ваш браузер, чтобы загрузить и отобразить его содержимое:

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]

Быстрый тест

Заметьте, я не использую уже существующие JSONPathили, ObjectMapperтаким образом, результаты неточные - в любом случае, это очень грубое сравнение:

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"))));
}

378 мс, чтобы получить «info.contact.email» с JSONPath, 756
мс, чтобы просто загрузить весь документ, в противном случае
896 мс, чтобы полностью отобразить его полностью с Джексоном и получить доступ к определенному полю.

Автор: ccjmne Размещён: 04.10.2014 10:06

5 плюса

4742 Репутация автора

Если вы используете JSON-файлы в своем приложении, тогда полезен следующий фрагмент кода:

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

и если вы используете Gson jars, тот же код будет выглядеть следующим образом:

Gson gson = new GsonBuilder().create();
Map jsonMap = gson.fromJson(json, Map.class);
System.out.println(jsonMap.get("contentType"));
Автор: Prasad Khode Размещён: 04.10.2014 12:37

1 плюс

195 Репутация автора

Другой способ это:

String json = "{\"contentType\": \"foo\", \"fooField1\": ... }";
JsonNode parent= new ObjectMapper().readTree(json);
String content = parent.get("contentType").asText();
Автор: xyz Размещён: 08.11.2018 11:29

0 плюса

45 Репутация автора

Просто хочу обновить для 2019. Я нашел следующее самое простое, чтобы подразумевать:

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

Я бы предложил использовать pathвместо getas getбросает NPE, где path возвращает значение по умолчанию 0 или "", с которым безопаснее работать, если правильно настроить синтаксический анализ в первый раз.

Мои 0,02 доллара

Автор: Rohan Kumar Размещён: 13.06.2019 06:17
Вопросы из категории :
32x32