From c90d1aafa86be5518d56591b6db3595443ef16b2 Mon Sep 17 00:00:00 2001 From: Anton Pinsky Date: Sat, 4 Nov 2023 16:55:36 +0100 Subject: [PATCH] [#625]Wrote the missing methods in JsonStructureToParserAdapter and tests for them; more fulfill of the JSON-P's JsonParser Signed-off-by: Anton Pinsky --- .../jsonstructure/JsonStructureIterator.java | 6 +- .../JsonStructureToParserAdapter.java | 217 ++++++++-- .../PolymorphismWithJsonValuesTest.java | 75 ++++ .../JsonStructureToParserAdapterTest.java | 400 +++++++++++++++++- 4 files changed, 670 insertions(+), 28 deletions(-) create mode 100644 src/test/java/org/eclipse/yasson/customization/polymorphism/PolymorphismWithJsonValuesTest.java diff --git a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureIterator.java b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureIterator.java index 1e402b57d..70bcd3619 100644 --- a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureIterator.java +++ b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -67,9 +67,11 @@ JsonParser.Event getValueEvent(JsonValue value) { case NUMBER: return JsonParser.Event.VALUE_NUMBER; case STRING: + return JsonParser.Event.VALUE_STRING; case TRUE: + return JsonParser.Event.VALUE_TRUE; case FALSE: - return JsonParser.Event.VALUE_STRING; + return JsonParser.Event.VALUE_FALSE; case OBJECT: return JsonParser.Event.START_OBJECT; case ARRAY: diff --git a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureToParserAdapter.java b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureToParserAdapter.java index dfa9d64f4..29f89501a 100644 --- a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureToParserAdapter.java +++ b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureToParserAdapter.java @@ -13,10 +13,22 @@ package org.eclipse.yasson.internal.jsonstructure; import java.math.BigDecimal; +import java.util.AbstractMap; import java.util.ArrayDeque; import java.util.Deque; +import java.util.EnumSet; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import jakarta.json.JsonArray; +import jakarta.json.JsonException; import jakarta.json.JsonNumber; import jakarta.json.JsonObject; import jakarta.json.JsonStructure; @@ -28,19 +40,27 @@ import org.eclipse.yasson.internal.properties.MessageKeys; import org.eclipse.yasson.internal.properties.Messages; +import static java.util.Spliterator.ORDERED; + /** * Adapter for {@link JsonParser}, that reads a {@link JsonStructure} content tree instead of JSON text. - * + *

* Yasson and jsonb API components are using {@link JsonParser} as its input API. * This adapter allows deserialization of {@link JsonStructure} into java content tree using same components * as when parsing JSON text. */ public class JsonStructureToParserAdapter implements JsonParser { - private Deque iterators = new ArrayDeque<>(); + private static final EnumSet GET_STRING_EVENTS = EnumSet.of(Event.KEY_NAME, Event.VALUE_STRING, Event.VALUE_NUMBER); + + private static final EnumSet NOT_GET_VALUE_EVENT_ENUM_SET = EnumSet.of(JsonParser.Event.END_OBJECT, JsonParser.Event.END_ARRAY); + + private final Deque iterators = new ArrayDeque<>(); private final JsonStructure rootStructure; + private Event currentEvent; + /** * Creates new {@link JsonStructure} parser. * @@ -52,35 +72,51 @@ public JsonStructureToParserAdapter(JsonStructure structure) { @Override public boolean hasNext() { - return iterators.peek().hasNext(); + JsonStructureIterator iterator = iterators.peek(); + return (iterator != null) && iterator.hasNext(); } @Override public Event next() { if (iterators.isEmpty()) { - if (rootStructure instanceof JsonObject) { - iterators.push(new JsonObjectIterator((JsonObject) rootStructure)); - return Event.START_OBJECT; - } else if (rootStructure instanceof JsonArray) { - iterators.push(new JsonArrayIterator((JsonArray) rootStructure)); - return Event.START_ARRAY; - } + currentEvent = pushIntoIterators(rootStructure, rootStructure instanceof JsonObject, Event.START_OBJECT, + rootStructure instanceof JsonArray, Event.START_ARRAY); + return currentEvent; } JsonStructureIterator current = iterators.peek(); - Event next = current.next(); - if (next == Event.START_OBJECT) { - iterators.push(new JsonObjectIterator((JsonObject) current.getValue())); - } else if (next == Event.START_ARRAY) { - iterators.push(new JsonArrayIterator((JsonArray) current.getValue())); - } else if (next == Event.END_OBJECT || next == Event.END_ARRAY) { + currentEvent = current.next(); + pushIntoIterators(current.getValue(), currentEvent == Event.START_OBJECT, null, currentEvent == Event.START_ARRAY, null); + if (currentEvent == Event.END_OBJECT || currentEvent == Event.END_ARRAY) { iterators.pop(); } - return next; + return currentEvent; + } + + private Event pushIntoIterators(JsonValue value, boolean isObject, Event objectEvent, boolean isArray, Event arrayEvent) { + if (isObject) { + iterators.push(new JsonObjectIterator((JsonObject) value)); + return objectEvent; + } else if (isArray) { + iterators.push(new JsonArrayIterator((JsonArray) value)); + return arrayEvent; + } + return null; + } + + @Override + public Event currentEvent() { + return currentEvent; } @Override public String getString() { - return iterators.peek().getString(); + JsonStructureIterator iterator = iterators.peek(); + if (iterator == null || !GET_STRING_EVENTS.contains(currentEvent)) { + throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "getString() call with current event: " + + (iterator == null ? "null" : currentEvent) + "; should be in " + GET_STRING_EVENTS)); + } else { + return iterator.getString(); + } } @Override @@ -111,12 +147,15 @@ public JsonObject getObject() { iterators.pop(); return current.getValue().asJsonObject(); } else { - throw new JsonbException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Outside of object context")); + throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Outside of object context")); } } private JsonNumber getJsonNumberValue() { JsonStructureIterator iterator = iterators.peek(); + if (iterator == null) { + throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Call of the number method on empty context")); + } JsonValue value = iterator.getValue(); if (value.getValueType() != JsonValue.ValueType.NUMBER) { throw iterator.createIncompatibleValueError(); @@ -130,20 +169,148 @@ public JsonLocation getLocation() { } @Override - public void skipArray() { - if (!iterators.isEmpty()) { - JsonStructureIterator current = iterators.peek(); - if (current instanceof JsonArrayIterator) { - iterators.pop(); + public JsonValue getValue() { + if (currentEvent == null || NOT_GET_VALUE_EVENT_ENUM_SET.contains(currentEvent)) { + throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "getValue() call with current event: " + + currentEvent + "; should not be in " + NOT_GET_VALUE_EVENT_ENUM_SET)); + } else { + JsonStructureIterator iterator = iterators.peek(); + if (iterator == null) { + throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "getValue() call on empty context")); + } else { + switch (currentEvent) { + case START_OBJECT: + return getObject(); + case START_ARRAY: + return getArray(); + default: + return iterator.getValue(); + } + } + } + } + + @Override + public JsonArray getArray() { + JsonStructureIterator current = iterators.peek(); + if (current instanceof JsonArrayIterator) { + //Remove child iterator as getArray() method contract says + iterators.pop(); + current = iterators.peek(); + if (current == null) { + throw new NoSuchElementException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "No more elements in JSON structure")); } + return current.getValue().asJsonArray(); + } else { + throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Outside of array context")); + } + } + + @Override + public Stream getArrayStream() { + JsonStructureIterator current = iterators.peek(); + if (current instanceof JsonArrayIterator) { + return StreamSupport.stream(new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE, ORDERED) { + public Spliterator trySplit() { + return null; + } + + public boolean tryAdvance(Consumer action) { + Objects.requireNonNull(action); + if (!JsonStructureToParserAdapter.this.hasNext() || JsonStructureToParserAdapter.this.next() == Event.END_ARRAY) { + return false; + } else { + action.accept(JsonStructureToParserAdapter.this.getValue()); + return true; + } + } + }, false); + } else { + throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Outside of array context")); } } + @Override + public Stream> getObjectStream() { + JsonStructureIterator current = iterators.peek(); + if (current instanceof JsonObjectIterator) { + return StreamSupport.stream(new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE, ORDERED) { + public Spliterator> trySplit() { + return null; + } + + public boolean tryAdvance(Consumer> action) { + Objects.requireNonNull(action); + if (!JsonStructureToParserAdapter.this.hasNext()) { + return false; + } else { + Event e = JsonStructureToParserAdapter.this.next(); + if (e == Event.END_OBJECT) { + return false; + } else if (e != Event.KEY_NAME) { + throw new JsonException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Cannot read object key")); + } else { + String key = JsonStructureToParserAdapter.this.getString(); + if (!JsonStructureToParserAdapter.this.hasNext()) { + throw new JsonException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Cannot read object value")); + } else { + JsonStructureToParserAdapter.this.next(); + JsonValue value = JsonStructureToParserAdapter.this.getValue(); + action.accept(new AbstractMap.SimpleImmutableEntry<>(key, value)); + return true; + } + } + } + } + }, false); + } else { + throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Outside of object context")); + } + } + + @Override + public Stream getValueStream() { + if (iterators.isEmpty()) { + //JsonParserImpl delivers the whole object - so we have to do this the same way + JsonStructureToParserAdapter.this.next(); + return StreamSupport.stream(new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE, ORDERED) { + public Spliterator trySplit() { + return null; + } + + public boolean tryAdvance(Consumer action) { + Objects.requireNonNull(action); + if (!JsonStructureToParserAdapter.this.hasNext()) { + return false; + } else { + //JsonParserImpl delivers the whole object - so we have to do this the same way + /*JsonStructureToParserAdapter.this.next();*/ + JsonValue value = JsonStructureToParserAdapter.this.getValue(); + action.accept(value); + return true; + } + } + }, false); + } else { + throw new IllegalStateException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "getValueStream can be only called at the root level of JSON structure")); + } + } + + @Override + public void skipArray() { + skipJsonPart(iterator -> iterator instanceof JsonArrayIterator); + } + @Override public void skipObject() { + skipJsonPart(iterator -> iterator instanceof JsonObjectIterator); + } + + private void skipJsonPart(Predicate predicate) { + Objects.requireNonNull(predicate); if (!iterators.isEmpty()) { JsonStructureIterator current = iterators.peek(); - if (current instanceof JsonObjectIterator) { + if (predicate.test(current)) { iterators.pop(); } } diff --git a/src/test/java/org/eclipse/yasson/customization/polymorphism/PolymorphismWithJsonValuesTest.java b/src/test/java/org/eclipse/yasson/customization/polymorphism/PolymorphismWithJsonValuesTest.java new file mode 100644 index 000000000..f3674d466 --- /dev/null +++ b/src/test/java/org/eclipse/yasson/customization/polymorphism/PolymorphismWithJsonValuesTest.java @@ -0,0 +1,75 @@ +package org.eclipse.yasson.customization.polymorphism; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import org.eclipse.yasson.Jsonbs; +import org.junit.jupiter.api.Test; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.annotation.JsonbSubtype; +import jakarta.json.bind.annotation.JsonbTypeInfo; + +public class PolymorphismWithJsonValuesTest { + + /** + * This test shows that the default Jsonb implementation support polymorphism with JsonValues. + * See GitHub issue + */ + @Test + public void testObjectWithJsonValue() { + Jsonb jsonb = Jsonbs.defaultJsonb; + var container = new PolyContainer.JsValueContainer(); + String containerSerialized = jsonb.toJson(container); + assertEquals("{\"type\":\"jsv\",\"jsonArray\":[\"an array json string\"],\"jsonBigDecimalValue\":10,\"jsonBigIntegerValue\":10," + + "\"jsonDoubleValue\":1.0,\"jsonIntValue\":1,\"jsonLongValue\":1,\"jsonObject\":{\"field\":\"an object json string\"}," + + "\"jsonStringValue\":\"a json string\"}", containerSerialized); + + var deserializedDirectly = jsonb.fromJson(containerSerialized, PolyContainer.JsValueContainer.class);//good + assertInstanceOf(PolyContainer.JsValueContainer.class, deserializedDirectly); + assertEquals(container.jsonStringValue, deserializedDirectly.jsonStringValue); + assertEquals(container.jsonIntValue, deserializedDirectly.jsonIntValue); + assertEquals(container.jsonLongValue, deserializedDirectly.jsonLongValue); + assertEquals(container.jsonDoubleValue, deserializedDirectly.jsonDoubleValue); + assertEquals(container.jsonBigDecimalValue, deserializedDirectly.jsonBigDecimalValue); + assertEquals(container.jsonBigIntegerValue, deserializedDirectly.jsonBigIntegerValue); + assertEquals(container.jsonArray, deserializedDirectly.jsonArray); + assertEquals(container.jsonObject, deserializedDirectly.jsonObject); + + var deserializedFromPoly = jsonb.fromJson(containerSerialized, PolyContainer.class);//bad + assertInstanceOf(PolyContainer.JsValueContainer.class, deserializedFromPoly); + assertEquals(container.jsonStringValue, ((PolyContainer.JsValueContainer)deserializedFromPoly).jsonStringValue); + assertEquals(container.jsonIntValue, ((PolyContainer.JsValueContainer)deserializedFromPoly).jsonIntValue); + assertEquals(container.jsonLongValue, ((PolyContainer.JsValueContainer)deserializedFromPoly).jsonLongValue); + assertEquals(container.jsonDoubleValue, ((PolyContainer.JsValueContainer)deserializedFromPoly).jsonDoubleValue); + assertEquals(container.jsonBigDecimalValue, ((PolyContainer.JsValueContainer)deserializedFromPoly).jsonBigDecimalValue); + assertEquals(container.jsonBigIntegerValue, ((PolyContainer.JsValueContainer)deserializedFromPoly).jsonBigIntegerValue); + assertEquals(container.jsonArray, ((PolyContainer.JsValueContainer)deserializedFromPoly).jsonArray); + assertEquals(container.jsonObject, ((PolyContainer.JsValueContainer)deserializedFromPoly).jsonObject); + } + + @JsonbTypeInfo( + key = "type", + value = {@JsonbSubtype(alias = "jsv", type = PolyContainer.JsValueContainer.class)} + ) + interface PolyContainer { + class JsValueContainer implements PolyContainer { + //fields should not be final, otherwise the test couldn't test + public /*final*/ JsonValue jsonStringValue = Json.createValue("a json string"); + public /*final*/ JsonValue jsonIntValue = Json.createValue(1); + public /*final*/ JsonValue jsonLongValue = Json.createValue(1L); + public /*final*/ JsonValue jsonDoubleValue = Json.createValue(1d); + public /*final*/ JsonValue jsonBigDecimalValue = Json.createValue(BigDecimal.TEN); + public /*final*/ JsonValue jsonBigIntegerValue = Json.createValue(BigInteger.TEN); + public /*final*/ JsonArray jsonArray = Json.createArrayBuilder().add("an array json string").build(); + public /*final*/ JsonObject jsonObject = Json.createObjectBuilder().add("field", "an object json string").build(); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java b/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java index d3025a2eb..0137d7951 100644 --- a/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java +++ b/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java @@ -12,27 +12,60 @@ package org.eclipse.yasson.jsonstructure; +import org.eclipse.yasson.internal.jsonstructure.JsonStructureToParserAdapter; +import org.hamcrest.Matcher; import org.junit.jupiter.api.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.junit.jupiter.api.Assertions.*; import static org.eclipse.yasson.Jsonbs.*; import org.eclipse.yasson.TestTypeToken; import org.eclipse.yasson.YassonJsonb; +import jakarta.json.Json; import jakarta.json.JsonArray; import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; import jakarta.json.spi.JsonProvider; +import jakarta.json.stream.JsonParser; +import jakarta.json.stream.JsonParserFactory; + +import java.io.StringReader; import java.math.BigDecimal; +import java.math.BigInteger; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collector; +import java.util.stream.Collectors; public class JsonStructureToParserAdapterTest { - private final JsonProvider jsonProvider = JsonProvider.provider(); + private static final EnumSet GET_STRING_EVENT_ENUM_SET = + EnumSet.of(JsonParser.Event.KEY_NAME, JsonParser.Event.VALUE_STRING, JsonParser.Event.VALUE_NUMBER); + + private static final EnumSet NOT_GET_VALUE_EVENT_ENUM_SET = EnumSet.of(JsonParser.Event.END_OBJECT, JsonParser.Event.END_ARRAY); + + private static final Collector, ?, ArrayList> MAP_TO_LIST_COLLECTOR = Collector.of(ArrayList::new, + (list, entry) -> { + list.add(entry.getKey()); + list.add(entry.getValue().toString()); + }, + (left, right) -> { + left.addAll(right); + return left; + }, + Collector.Characteristics.IDENTITY_FINISH); + + private static final JsonProvider jsonProvider = JsonProvider.provider(); @Test public void testBasicJsonObject() { @@ -274,4 +307,369 @@ public void testCustomJsonbDeserializer() throws Exception { assertEquals("String value 2", result.getInner().getInnerSecond()); } } + + @Nested + public class DirectParserTests { + @Test + public void testNumbers() { + JsonObject jsonObject = jsonProvider.createObjectBuilder() + .add("int", 1) + .add("long", 1L) + .add("double", 1d) + .add("BigInteger", BigInteger.TEN) + .add("BigDecimal", BigDecimal.TEN) + .build(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + parser.next(); + parser.next(); + parser.getString(); + parser.next(); + assertTrue(parser.isIntegralNumber()); + assertEquals(1, parser.getInt()); + + parser.next(); + parser.getString(); + parser.next(); + assertTrue(parser.isIntegralNumber()); + assertEquals(1L, parser.getLong()); + + parser.next(); + parser.getString(); + parser.next(); + assertFalse(parser.isIntegralNumber()); + assertEquals(BigDecimal.valueOf(1d), parser.getBigDecimal()); + + parser.next(); + parser.getString(); + parser.next(); + assertTrue(parser.isIntegralNumber()); + assertEquals(BigDecimal.TEN, parser.getBigDecimal()); + + parser.next(); + parser.getString(); + parser.next(); + assertTrue(parser.isIntegralNumber()); + assertEquals(BigDecimal.TEN, parser.getBigDecimal()); + } + } + + @Test + public void testParser_getString(){ + JsonObject jsonObject = TestData.createFamilyPerson(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + List values = new ArrayList<>(); + parser.next(); + while (parser.hasNext()) { + JsonParser.Event event = parser.next(); + if (GET_STRING_EVENT_ENUM_SET.contains(event)) { + String strValue = Objects.toString(parser.getString(), "null"); + values.add(strValue); + } + } + + assertThat(values,TestData.FAMILY_MATCHER_WITH_NO_QUOTATION); + } + } + + @Test + public void testParser_getValue(){ + JsonObject jsonObject = TestData.createFamilyPerson(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + List values = new ArrayList<>(); + parser.next(); + while (parser.hasNext()) { + JsonParser.Event event = parser.next(); + if (!NOT_GET_VALUE_EVENT_ENUM_SET.contains(event)) { + String strValue = Objects.toString(parser.getValue(), "null"); + values.add(strValue); + } + } + + //should look like this with a correct implementation -> FAMILY_MATCHER_KEYS_WITH_QUOTATION + /*assertThat(values, contains("\"name\"", "\"John\"", "\"surname\"", "\"Smith\"", "\"age\"", "30", "\"married\"", "true", + "\"wife\"", "{\"name\":\"Deborah\",\"surname\":\"Harris\"}", "\"children\"", "[\"Jack\",\"Mike\"]"));*/ + assertThat(values, contains("\"John\"", "\"John\"", "\"Smith\"", "\"Smith\"", "30", "30", "true", "true", + "{\"name\":\"Deborah\",\"surname\":\"Harris\"}", "{\"name\":\"Deborah\",\"surname\":\"Harris\"}", + "[\"Jack\",\"Mike\"]", "[\"Jack\",\"Mike\"]")); + } + } + + @Test + public void testSkipArray() { + JsonObject jsonObject = TestData.createObjectWithArrays(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + parser.next(); + parser.next(); + parser.getString(); + parser.next(); + parser.skipArray(); + parser.next(); + String key = parser.getString(); + + assertEquals("secondElement", key); + } + } + + @Test + public void testSkipObject() { + JsonObject jsonObject = TestData.createJsonObject(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + parser.next(); + parser.next(); + parser.getString(); + parser.next(); + parser.skipObject(); + parser.next(); + String key = parser.getString(); + + assertEquals("secondPerson", key); + } + } + } + + @Nested + public class StreamTests { + @Test + public void testGetValueStream_GetOneElement() { + JsonObject jsonObject = TestData.createFamilyPerson(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + JsonString name = (JsonString) parser.getValueStream() + .map(JsonValue::asJsonObject) + .map(JsonObject::values) + .findFirst() + .orElseThrow() + .stream() + .filter(e -> e.getValueType() == JsonValue.ValueType.STRING) + .findFirst() + .orElseThrow(() -> new RuntimeException("Name not found")); + + assertEquals("John", name.getString()); + } + } + + @Test + public void testGetValueStream_GetList() { + JsonObject jsonObject = TestData.createFamilyPerson(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + List values = parser.getValueStream().map(value -> Objects.toString(value, "null")).collect(Collectors.toList()); + + assertThat(values, contains(TestData.JSON_FAMILY_STRING)); + } + } + + @Test + public void testGetArrayStream_GetOneElement() { + JsonObject jsonObject = TestData.createObjectWithArrays(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + parser.next(); + parser.next(); + String key = parser.getString(); + parser.next(); + JsonString element = (JsonString) parser.getArrayStream().filter(e -> e.getValueType() == JsonValue.ValueType.STRING) + .findFirst() + .orElseThrow(() -> new RuntimeException("Element not found")); + + assertEquals("first", element.getString()); + assertEquals("firstElement", key); + } + } + + @Test + public void testGetArrayStream_GetList() { + JsonObject jsonObject = TestData.createObjectWithArrays(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + parser.next(); + parser.next(); + String key = parser.getString(); + parser.next(); + List values = parser.getArrayStream().map(value -> Objects.toString(value, "null")).collect(Collectors.toList()); + + assertThat(values, TestData.ARRAY_STREAM_MATCHER); + assertEquals("firstElement", key); + } + } + + @Test + public void testGetObjectStream_GetOneElement() { + JsonObject jsonObject = TestData.createJsonObject(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + parser.next(); + String surname = parser.getObjectStream().filter(e -> e.getKey().equals("firstPerson")) + .map(Map.Entry::getValue) + .map(JsonValue::asJsonObject) + .map(obj -> obj.getString("surname")) + .findFirst() + .orElseThrow(() -> new RuntimeException("Surname not found")); + + assertEquals("Smith", surname); + } + } + + @Test + public void testGetObjectStream_GetList() { + JsonObject jsonObject = TestData.createFamilyPerson(); + + try (JsonStructureToParserAdapter parser = new JsonStructureToParserAdapter(jsonObject)) { + parser.next(); + List values = parser.getObjectStream().collect(MAP_TO_LIST_COLLECTOR); + + assertThat(values, TestData.FAMILY_MATCHER_KEYS_WITHOUT_QUOTATION); + } + } + } + + @Nested + public class JSONPStandardParserTests { + @Test + public void testStandardStringParser_getValueStream() { + try (JsonParser parser = Json.createParser(new StringReader(TestData.JSON_FAMILY_STRING))) { + List values = parser.getValueStream().map(value -> Objects.toString(value, "null")).collect(Collectors.toList()); + + assertThat(values, contains(TestData.JSON_FAMILY_STRING)); + } + } + + @Test + public void testStandardStringParser_getArrayStream() { + try (JsonParser parser = Json.createParser(new StringReader("{\"firstElement\":[\"first\", \"second\"],\"secondElement\":[\"third\", \"fourth\"]}"))) { + parser.next(); + parser.next(); + String key = parser.getString(); + parser.next(); + List values = parser.getArrayStream().map(value -> Objects.toString(value, "null")).collect(Collectors.toList()); + + assertThat(values, TestData.ARRAY_STREAM_MATCHER); + assertEquals("firstElement", key); + } + } + + @Test + public void testStandardStringParser_getObjectStream() { + try (JsonParser parser = Json.createParser(new StringReader(TestData.JSON_FAMILY_STRING))) { + + parser.next(); + List values = parser.getObjectStream().collect(MAP_TO_LIST_COLLECTOR); + + assertThat(values, TestData.FAMILY_MATCHER_KEYS_WITHOUT_QUOTATION); + } + } + + @Test + public void testStandardStringParser_getValue() { + try (JsonParser parser = Json.createParser(new StringReader(TestData.JSON_FAMILY_STRING))) { + List values = new ArrayList<>(); + parser.next(); + while (parser.hasNext()) { + JsonParser.Event event = parser.next(); + if (!NOT_GET_VALUE_EVENT_ENUM_SET.contains(event)) { + String strValue = Objects.toString(parser.getValue(), "null"); + values.add(strValue); + } + } + + assertThat(values, TestData.FAMILY_MATCHER_KEYS_WITH_QUOTATION); + } + } + + @Test + public void testStandardStringParser_getString() { + try (JsonParser parser = Json.createParser(new StringReader(TestData.JSON_FAMILY_STRING))) { + List values = new ArrayList<>(); + parser.next(); + while (parser.hasNext()) { + JsonParser.Event event = parser.next(); + if (GET_STRING_EVENT_ENUM_SET.contains(event)) { + String strValue = Objects.toString(parser.getString(), "null"); + values.add(strValue); + } + } + + assertThat(values, TestData.FAMILY_MATCHER_WITH_NO_QUOTATION); + } + } + + @Test + public void testStandardStructureParser_getString() { + JsonParserFactory factory = Json.createParserFactory(Map.of()); + JsonObject jsonObject = TestData.createFamilyPerson(); + + try (JsonParser parser = factory.createParser(jsonObject)) { + List values = new ArrayList<>(); + parser.next(); + while (parser.hasNext()) { + JsonParser.Event event = parser.next(); + if (GET_STRING_EVENT_ENUM_SET.contains(event)) { + String strValue = Objects.toString(parser.getString(), "null"); + values.add(strValue); + } + } + + assertThat(values, TestData.FAMILY_MATCHER_WITH_NO_QUOTATION); + } + } + } + + private static class TestData { + private static final String JSON_FAMILY_STRING = "{\"name\":\"John\",\"surname\":\"Smith\",\"age\":30,\"married\":true," + + "\"wife\":{\"name\":\"Deborah\",\"surname\":\"Harris\"},\"children\":[\"Jack\",\"Mike\"]}"; + + private static final Matcher> FAMILY_MATCHER_KEYS_WITHOUT_QUOTATION = + contains("name", "\"John\"", "surname", "\"Smith\"", "age", "30", "married", "true", "wife", + "{\"name\":\"Deborah\",\"surname\":\"Harris\"}", "children", "[\"Jack\",\"Mike\"]"); + + private static final Matcher> FAMILY_MATCHER_KEYS_WITH_QUOTATION = + contains("\"name\"", "\"John\"", "\"surname\"", "\"Smith\"", "\"age\"", "30", "\"married\"", "true", + "\"wife\"", "{\"name\":\"Deborah\",\"surname\":\"Harris\"}", "\"children\"", "[\"Jack\",\"Mike\"]"); + + private static final Matcher> FAMILY_MATCHER_WITH_NO_QUOTATION = + contains("name", "John", "surname", "Smith", "age", "30", "married", + "wife", "name", "Deborah", "surname", "Harris", "children", "Jack", "Mike"); + + private static final Matcher> ARRAY_STREAM_MATCHER = contains("\"first\"", "\"second\""); + + private static JsonObject createFamilyPerson() { + return jsonProvider.createObjectBuilder() + .add("name", "John") + .add("surname", "Smith") + .add("age", 30) + .add("married", true) + .add("wife", createPerson("Deborah", "Harris")) + .add("children", createArray("Jack", "Mike")) + .build(); + } + + private static JsonObject createObjectWithArrays() { + return jsonProvider.createObjectBuilder() + .add("firstElement", createArray("first", "second")) + .add("secondElement", createArray("third", "fourth")) + .build(); + } + + private static JsonArrayBuilder createArray(String firstElement, String secondElement) { + return jsonProvider.createArrayBuilder().add(firstElement).add(secondElement); + } + + private static JsonObject createJsonObject() { + return jsonProvider.createObjectBuilder() + .add("firstPerson", createPerson("John", "Smith")) + .add("secondPerson", createPerson("Deborah", "Harris")) + .build(); + } + + private static JsonObjectBuilder createPerson(String name, String surname) { + return jsonProvider.createObjectBuilder() + .add("name", name) + .add("surname", surname); + } + } }