Для произвольного JSON я хотел бы получить значение одного поля contentType
. Как это сделать с помощью Jackson?
{
contentType: "foo",
fooField1: ...
}
{
contentType: "bar",
barArray: [...]
}
Похожие
Для произвольного JSON я хотел бы получить значение одного поля contentType
. Как это сделать с помощью Jackson?
{
contentType: "foo",
fooField1: ...
}
{
contentType: "bar",
barArray: [...]
}
Похожие
Учитывая, что у вас нет 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]
Имейте в виду, что это основное. Он не приводит типы данных (каждое возвращаемое значение является String
) и возвращает только конечные узлы.
Он обрабатывает InputStream
s, так что вы можете проверить его на каком-то гигантском документе JSON и увидеть, что он намного быстрее, чем ваш браузер, чтобы загрузить и отобразить его содержимое:
System.out.println(new JSONPath("info.contact.email")
.getWithin(new URL("http://test-api.rescuegroups.org/v5/public/swagger.php").openStream()));
// Optional[[email protected]]
Обратите внимание, я не повторно использовать любой уже существующий 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мс, чтобы прямо сопоставить его полностью с Джексоном и получить доступ к определенному полю
Если вы используете JSON-банки в своем приложении, полезен следующий фрагмент кода:
String json = "{\"contentType\": \"foo\", \"fooField1\": ... }";
JSONObject jsonObject = new JSONObject(json);
System.out.println(jsonObject.getString("contentType"));
и если вы используете баннеры Gson, то тот же код будет выглядеть следующим образом:
Gson gson = new GsonBuilder().create();
Map jsonMap = gson.fromJson(json, Map.class);
System.out.println(jsonMap.get("contentType"));
Другой способ это:
String json = "{\"contentType\": \"foo\", \"fooField1\": ... }";
JsonNode parent= new ObjectMapper().readTree(json);
String content = parent.get("contentType").asText();
Просто хочу обновить для 2019. Я нашел следующее самое простое, чтобы подразумевать:
//json can be file or String
JsonNode parent= new ObjectMapper().readTree(json);
String content = parent.path("contentType").asText();
Я бы предложил использовать path
вместо get
поскольку get
генерирует NPE, где path возвращает значение по умолчанию 0 или "", с которым безопаснее работать, если правильно настроить синтаксический анализ в первый раз.
Мои 0,02 доллара
моя проблема немного сложнее.. для объекта ниже мне нужно JSON String..
Ниже приведен мой класс, в котором есть несколько String и один JSON-объект, который нам нужно сохранить в БД в одной таблице.
public class TestTransaction extends TestTransaction {
private String radarId;
private String exclusionReason;
private Timestamp eventTs;
private JSONObject drivingAttributes;
Я использую Object mapper для преобразования этого объекта, но он дает мне Json внутри Json... это неправильно.. мне нужно конвертировать внутри объекта JSON в String, используя ниже 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);
неверный JSON:
{
"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
}
правильный JSON должен быть
{
"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
}