Support conversion of JsonObject and JsonArray from/to java pojo classes Signed-off-by: Roman Grigoriadi <roman.grigoriadi@oracle.com>
diff --git a/src/main/java/org/eclipse/yasson/YassonJsonb.java b/src/main/java/org/eclipse/yasson/YassonJsonb.java index ed3643a..570cd84 100644 --- a/src/main/java/org/eclipse/yasson/YassonJsonb.java +++ b/src/main/java/org/eclipse/yasson/YassonJsonb.java
@@ -9,6 +9,7 @@ ******************************************************************************/ package org.eclipse.yasson; +import javax.json.JsonStructure; import javax.json.bind.JsonbException; import javax.json.stream.JsonGenerator; import javax.json.stream.JsonParser; @@ -71,6 +72,47 @@ <T> T fromJson(JsonParser jsonParser, Type runtimeType) throws JsonbException; /** + * Reads a {@link JsonStructure} and and converts it into + * resulting java content tree. + * + * @param jsonStructure + * {@link JsonStructure} to be used as a source for conversion. + * @param type + * Type of the content tree's root object. + * @param <T> + * Type of the content tree's root object. + * + * @return the newly created root object of the java content tree + * + * @throws JsonbException + * If any unexpected error(s) occur(s) during conversion. + * @throws NullPointerException + * If any of the parameters is {@code null}. + */ + <T> T fromJsonStructure(JsonStructure jsonStructure, Class<T> type) throws JsonbException; + + /** + * Reads a {@link JsonStructure} and and converts it into + * resulting java content tree. + * + * @param jsonStructure + * {@link JsonStructure} to be used as a source for conversion. + * @param runtimeType + * Runtime type of the content tree's root object. + * @param <T> + * Type of the content tree's root object. + * + * @return the newly created root object of the java content tree + * + * @throws JsonbException + * If any unexpected error(s) occur(s) during deserialization. + * @throws NullPointerException + * If any of the parameters is {@code null}. + */ + <T> T fromJsonStructure(JsonStructure jsonStructure, Type runtimeType) throws JsonbException; + + + /** * Writes the object content tree with a specified {@link JsonGenerator}. * Provided json generator must be fully initialized, no further configurations are applied. * @@ -111,4 +153,41 @@ * @since JSON Binding 1.0 */ void toJson(Object object, Type runtimeType, JsonGenerator jsonGenerator) throws JsonbException; + + /** + * Serializes the object content tree to a {@link javax.json.JsonStructure}. + * + * @param object + * The object content tree to be serialized. + * + * @return The {@link JsonStructure} serialized from java content tree. + * + * @throws JsonbException If any unexpected problem occurs during the + * serialization. + * @throws NullPointerException + * If any of the parameters is {@code null}. + * + * @since JSON Binding 1.0 + */ + JsonStructure toJsonStructure(Object object) throws JsonbException; + + /** + * Serializes the object content tree to a {@link javax.json.JsonStructure}. + * + * @param object + * The object content tree to be serialized. + * + * @param runtimeType + * Runtime type of the content tree's root object. + * + * @return The {@link JsonStructure} serialized from java content tree. + * + * @throws JsonbException If any unexpected problem occurs during the + * serialization. + * @throws NullPointerException + * If any of the parameters is {@code null}. + * + * @since JSON Binding 1.0 + */ + JsonStructure toJsonStructure (Object object, Type runtimeType) throws JsonbException; }
diff --git a/src/main/java/org/eclipse/yasson/internal/JsonBinding.java b/src/main/java/org/eclipse/yasson/internal/JsonBinding.java index 6e7a53d..f8b3d75 100644 --- a/src/main/java/org/eclipse/yasson/internal/JsonBinding.java +++ b/src/main/java/org/eclipse/yasson/internal/JsonBinding.java
@@ -13,15 +13,23 @@ package org.eclipse.yasson.internal; import org.eclipse.yasson.YassonJsonb; +import org.eclipse.yasson.internal.jsonstructure.JsonGeneratorToStructureAdapter; +import org.eclipse.yasson.internal.jsonstructure.JsonStructureToParserAdapter; import org.eclipse.yasson.internal.properties.MessageKeys; import org.eclipse.yasson.internal.properties.Messages; +import javax.json.JsonStructure; import javax.json.bind.JsonbConfig; import javax.json.bind.JsonbException; import javax.json.spi.JsonProvider; import javax.json.stream.JsonGenerator; import javax.json.stream.JsonParser; -import java.io.*; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.HashMap; @@ -85,6 +93,18 @@ return deserialize(type, inputStreamParser(stream), unmarshaller); } + @Override + public <T> T fromJsonStructure(JsonStructure jsonStructure, Class<T> type) throws JsonbException { + JsonParser parser = new JsonbRiParser(new JsonStructureToParserAdapter(jsonStructure)); + return deserialize(type, parser, new Unmarshaller(jsonbContext)); + } + + @Override + public <T> T fromJsonStructure(JsonStructure jsonStructure, Type runtimeType) throws JsonbException { + JsonParser parser = new JsonbRiParser(new JsonStructureToParserAdapter(jsonStructure)); + return deserialize(runtimeType, parser, new Unmarshaller(jsonbContext)); + } + private JsonParser inputStreamParser(InputStream stream) { return new JsonbRiParser(jsonbContext.getJsonProvider().createParserFactory(createJsonpProperties(jsonbContext.getConfig())) .createParser(stream, @@ -163,6 +183,22 @@ marshaller.marshallWithoutClose(object, jsonGenerator); } + @Override + public JsonStructure toJsonStructure(Object object) throws JsonbException { + JsonGeneratorToStructureAdapter structureGenerator = new JsonGeneratorToStructureAdapter(jsonbContext.getJsonProvider()); + final Marshaller marshaller = new Marshaller(jsonbContext); + marshaller.marshall(object, structureGenerator); + return structureGenerator.getRootStructure(); + } + + @Override + public JsonStructure toJsonStructure(Object object, Type runtimeType) throws JsonbException { + JsonGeneratorToStructureAdapter structureGenerator = new JsonGeneratorToStructureAdapter(jsonbContext.getJsonProvider()); + final Marshaller marshaller = new Marshaller(jsonbContext, runtimeType); + marshaller.marshall(object, structureGenerator); + return structureGenerator.getRootStructure(); + } + private JsonGenerator streamGenerator(OutputStream stream) { Map<String, ?> factoryProperties = createJsonpProperties(jsonbContext.getConfig()); final String encoding = (String) jsonbContext.getConfig().getProperty(JsonbConfig.ENCODING).orElse("UTF-8");
diff --git a/src/main/java/org/eclipse/yasson/internal/JsonbRiParser.java b/src/main/java/org/eclipse/yasson/internal/JsonbRiParser.java index 29e2183..18e31d2 100644 --- a/src/main/java/org/eclipse/yasson/internal/JsonbRiParser.java +++ b/src/main/java/org/eclipse/yasson/internal/JsonbRiParser.java
@@ -25,6 +25,7 @@ import java.math.BigDecimal; import java.util.Arrays; import java.util.Map; +import java.util.Objects; import java.util.Stack; import java.util.stream.Stream; @@ -76,6 +77,7 @@ } private void setLastKeyName(String lastKeyName) { + Objects.requireNonNull(lastKeyName); this.lastKeyName = lastKeyName; }
diff --git a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonArrayBuilder.java b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonArrayBuilder.java new file mode 100644 index 0000000..e977b2f --- /dev/null +++ b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonArrayBuilder.java
@@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2019 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 v1.0 and Eclipse Distribution License v. 1.0 + * which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + ******************************************************************************/ +package org.eclipse.yasson.internal.jsonstructure; + +import javax.json.JsonArray; +import javax.json.JsonStructure; +import javax.json.JsonValue; +import javax.json.spi.JsonProvider; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * Builds {@link JsonArray}. Delegates to {@link javax.json.JsonArrayBuilder}. + */ +class JsonArrayBuilder extends JsonStructureBuilder { + + private final javax.json.JsonArrayBuilder arrayBuilder; + + /** + * Create instance with cached provider. + * @param provider Json provider to create JsonArrayBuilder on. + */ + JsonArrayBuilder(JsonProvider provider) { + this.arrayBuilder = provider.createArrayBuilder(); + } + + @Override + JsonArray build() { + return arrayBuilder.build(); + } + + @Override + void write(JsonValue value) { + arrayBuilder.add(value); + } + + @Override + void write(String value) { + arrayBuilder.add(value); + } + + @Override + void write(BigDecimal value) { + arrayBuilder.add(value); + } + + @Override + void write(BigInteger value) { + arrayBuilder.add(value); + } + + @Override + void write(int value) { + arrayBuilder.add(value); + } + + @Override + void write(long value) { + arrayBuilder.add(value); + } + + @Override + void write(double value) { + arrayBuilder.add(value); + } + + @Override + void write(boolean value) { + arrayBuilder.add(value); + } + + @Override + void writeNull() { + arrayBuilder.addNull(); + } + + @Override + void put(JsonStructure structure) { + arrayBuilder.add(structure); + } +}
diff --git a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonArrayIterator.java b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonArrayIterator.java new file mode 100644 index 0000000..c6d0b0f --- /dev/null +++ b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonArrayIterator.java
@@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2019 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 v1.0 and Eclipse Distribution License v. 1.0 + * which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + ******************************************************************************/ +package org.eclipse.yasson.internal.jsonstructure; + +import org.eclipse.yasson.internal.properties.MessageKeys; +import org.eclipse.yasson.internal.properties.Messages; + +import javax.json.JsonArray; +import javax.json.JsonString; +import javax.json.JsonValue; +import javax.json.bind.JsonbException; +import javax.json.stream.JsonParser; +import java.util.Iterator; + +/** + * Iterates over {@link JsonArray}. + */ +public class JsonArrayIterator extends JsonStructureIterator { + + private final Iterator<JsonValue> valueIterator; + + private JsonValue currentValue; + + public JsonArrayIterator(JsonArray jsonArray) { + this.valueIterator = jsonArray.iterator(); + } + + /** + * After {@link JsonParser.Event} END_ARRAY is returned from next() iterator is removed from the stack + * @return always true + */ + @Override + public boolean hasNext() { + return true; + } + + @Override + public JsonParser.Event next() { + if (valueIterator.hasNext()) { + currentValue = valueIterator.next(); + return getValueEvent(currentValue); + } + return JsonParser.Event.END_ARRAY; + } + + @Override + JsonValue getValue() { + return currentValue; + } + + @Override + JsonbException createIncompatibleValueError() { + return new JsonbException(Messages.getMessage(MessageKeys.NUMBER_INCOMPATIBLE_VALUE_TYPE_ARRAY, getValue().getValueType())); + } + + @Override + String getString() { + if (currentValue instanceof JsonString) { + return ((JsonString) currentValue).getString(); + } + return currentValue.toString(); + } +}
diff --git a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonGeneratorToStructureAdapter.java b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonGeneratorToStructureAdapter.java new file mode 100644 index 0000000..d32d42b --- /dev/null +++ b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonGeneratorToStructureAdapter.java
@@ -0,0 +1,228 @@ +/******************************************************************************* + * Copyright (c) 2019 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 v1.0 and Eclipse Distribution License v. 1.0 + * which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + ******************************************************************************/ +package org.eclipse.yasson.internal.jsonstructure; + +import org.eclipse.yasson.internal.properties.MessageKeys; +import org.eclipse.yasson.internal.properties.Messages; + +import javax.json.JsonStructure; +import javax.json.JsonValue; +import javax.json.bind.JsonbException; +import javax.json.spi.JsonProvider; +import javax.json.stream.JsonGenerator; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Stack; + +/** + * Adapter for {@link JsonGenerator}, that builds a {@link JsonStructure} content tree instead of JSON text. + * + * Yasson and jsonb API components are using {@link JsonGenerator} as its output API. + * This adapter allows serialization of java content tree into {@link JsonStructure} using same components + * as when generating JSON text. + */ +public class JsonGeneratorToStructureAdapter implements JsonGenerator { + + private final Stack<JsonStructureBuilder> builders; + + private JsonStructure root; + + private final JsonProvider provider; + + /** + * Default constructor, jsonp builders are created internally. + * + * @param provider Cached json provider to create builders on. + */ + public JsonGeneratorToStructureAdapter(JsonProvider provider) { + this.builders = new Stack<>(); + this.provider = provider; + } + + @Override + public JsonGenerator writeStartObject() { + builders.push(new JsonObjectBuilder(provider)); + return this; + } + + @Override + public JsonGenerator writeStartObject(String name) { + getJsonObjectBuilder(name).writeKey(name); + builders.push(new JsonObjectBuilder(provider)); + return this; + } + + @Override + public JsonGenerator writeKey(String name) { + getJsonObjectBuilder(name).writeKey(name); + return this; + } + + @Override + public JsonGenerator writeStartArray() { + builders.push(new JsonArrayBuilder(provider)); + return this; + } + + @Override + public JsonGenerator writeStartArray(String name) { + getJsonObjectBuilder(name).writeKey(name); + builders.push(new JsonArrayBuilder(provider)); + return this; + } + + @Override + public JsonGenerator write(String name, JsonValue value) { + getJsonObjectBuilder(name).write(name, value); + return this; + } + + @Override + public JsonGenerator write(String name, String value) { + getJsonObjectBuilder(name).write(name, value); + return this; + } + + @Override + public JsonGenerator write(String name, BigInteger value) { + getJsonObjectBuilder(name).write(name, value); + return this; + } + + @Override + public JsonGenerator write(String name, BigDecimal value) { + getJsonObjectBuilder(name).write(name, value); + return this; + } + + @Override + public JsonGenerator write(String name, int value) { + getJsonObjectBuilder(name).write(name, value); + return this; + } + + @Override + public JsonGenerator write(String name, long value) { + getJsonObjectBuilder(name).write(name, value); + return this; + } + + @Override + public JsonGenerator write(String name, double value) { + getJsonObjectBuilder(name).write(name, value); + return this; + } + + @Override + public JsonGenerator write(String name, boolean value) { + getJsonObjectBuilder(name).write(name, value); + return this; + } + + private JsonObjectBuilder getJsonObjectBuilder(String keyName) { + JsonStructureBuilder current = builders.peek(); + if (!(current instanceof JsonObjectBuilder)) { + throw new JsonbException(Messages.getMessage( + MessageKeys.INTERNAL_ERROR, "Can't write key [" + keyName + "] into " + current.getClass())); + } + return (JsonObjectBuilder) current; + } + + @Override + public JsonGenerator writeNull(String name) { + getJsonObjectBuilder(name).writeNull(name); + return this; + } + + @Override + public JsonGenerator writeEnd() { + JsonStructureBuilder builder = builders.pop(); + JsonStructure structure = builder.build(); + if (builders.isEmpty()) { + this.root = structure; + } else { + builders.peek().put(structure); + } + return this; + } + + @Override + public JsonGenerator write(JsonValue value) { + builders.peek().write(value); + return this; + } + + @Override + public JsonGenerator write(String value) { + builders.peek().write(value); + return this; + } + + @Override + public JsonGenerator write(BigDecimal value) { + builders.peek().write(value); + return this; + } + + @Override + public JsonGenerator write(BigInteger value) { + builders.peek().write(value); + return this; + } + + @Override + public JsonGenerator write(int value) { + builders.peek().write(value); + return this; + } + + @Override + public JsonGenerator write(long value) { + builders.peek().write(value); + return this; + } + + @Override + public JsonGenerator write(double value) { + builders.peek().write(value); + return this; + } + + @Override + public JsonGenerator write(boolean value) { + builders.peek().write(value); + return this; + } + + @Override + public JsonGenerator writeNull() { + builders.peek().writeNull(); + return this; + } + + @Override + public void close() { + //noop + } + + @Override + public void flush() { + //noop + } + + /** + * Root structure wrapping all data. + * @return root JsonStructure. + */ + public JsonStructure getRootStructure() { + return root; + } +}
diff --git a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonObjectBuilder.java b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonObjectBuilder.java new file mode 100644 index 0000000..dbebcbc --- /dev/null +++ b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonObjectBuilder.java
@@ -0,0 +1,203 @@ +/******************************************************************************* + * Copyright (c) 2019 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 v1.0 and Eclipse Distribution License v. 1.0 + * which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + ******************************************************************************/ +package org.eclipse.yasson.internal.jsonstructure; + +import org.eclipse.yasson.internal.properties.MessageKeys; +import org.eclipse.yasson.internal.properties.Messages; + +import javax.json.JsonStructure; +import javax.json.JsonValue; +import javax.json.bind.JsonbException; +import javax.json.spi.JsonProvider; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * Builds {@link javax.json.JsonObject} delegates to {@link javax.json.JsonObjectBuilder}, caches key when + * written without a value. + */ +class JsonObjectBuilder extends JsonStructureBuilder { + + private final javax.json.JsonObjectBuilder builder; + + private String nextKey; + + /** + * Create instance with cached provider. + * @param provider Json provider to create JsonObjectBuilder on. + */ + JsonObjectBuilder(JsonProvider provider) { + this.builder = provider.createObjectBuilder(); + } + + @Override + JsonStructure build() { + return builder.build(); + } + + @Override + void put(JsonStructure structure) { + builder.add(getNextKey(), structure); + } + + /** + * Puts another {@link JsonStructure} into current using provided key. + * @param name key to put JsonStructure under. + * @param structure JsonStructure to put. + */ + void put(String name, JsonStructure structure) { + builder.add(name, structure); + } + + @Override + void write(JsonValue value) { + builder.add(getNextKey(), value); + } + + @Override + void write(String value) { + builder.add(getNextKey(), value); + } + + @Override + void write(BigDecimal value) { + builder.add(getNextKey(), value); + } + + @Override + void write(BigInteger value) { + builder.add(getNextKey(), value); + } + + @Override + void write(int value) { + builder.add(getNextKey(), value); + } + + @Override + void write(long value) { + builder.add(getNextKey(), value); + } + + @Override + void write(double value) { + builder.add(getNextKey(), value); + } + + @Override + void write(boolean value) { + builder.add(getNextKey(), value); + } + + @Override + void writeNull() { + builder.addNull(getNextKey()); + } + + /** + * Write a key-value pair into current {@link javax.json.JsonObject}. + * @param name Key name to write value with. + * @param value A value to write. + */ + void write(String name, JsonValue value) { + builder.add(name, value); + } + + /** + * Write a key-value pair into current {@link javax.json.JsonObject}. + * @param name Key name to write value with. + * @param value A value to write. + */ + void write(String name, String value) { + builder.add(name, value); + } + + /** + * Write a key-value pair into current {@link javax.json.JsonObject}. + * @param name Key name to write value with. + * @param value A value to write. + */ + void write(String name, BigDecimal value) { + builder.add(name, value); + } + + /** + * Write a key-value pair into current {@link javax.json.JsonObject}. + * @param name Key name to write value with. + * @param value A value to write. + */ + void write(String name, BigInteger value) { + builder.add(name, value); + } + + /** + * Write a key-value pair into current {@link javax.json.JsonObject}. + * @param name Key name to write value with. + * @param value A value to write. + */ + void write(String name, int value) { + builder.add(name, value); + } + + /** + * Write a key-value pair into current {@link javax.json.JsonObject}. + * @param name Key name to write value with. + * @param value A value to write. + */ + void write(String name, long value) { + builder.add(name, value); + } + + /** + * Write a key-value pair into current {@link javax.json.JsonObject}. + * @param name Key name to write value with. + * @param value A value to write. + */ + void write(String name, double value) { + builder.add(name, value); + } + + /** + * Write a key-value pair into current {@link javax.json.JsonObject}. + * @param name Key name to write value with. + * @param value A value to write. + */ + void write(String name, boolean value) { + builder.add(name, value); + } + + /** + * Write a null into current {@link javax.json.JsonObject} with a given key. + * @param name Key name to write null with. + */ + void writeNull(String name) { + builder.addNull(name); + } + + /** + * Store a key for putting next value into built {@link javax.json.JsonObject}. + * @param key Key to store. + */ + void writeKey(String key) { + this.nextKey = key; + } + + + private String getNextKey() { + if (nextKey == null) { + throw new JsonbException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, + "Can't write a value without key name")); + } + String key = nextKey; + nextKey = null; + return key; + } +}
diff --git a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonObjectIterator.java b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonObjectIterator.java new file mode 100644 index 0000000..41917ed --- /dev/null +++ b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonObjectIterator.java
@@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2019 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 v1.0 and Eclipse Distribution License v. 1.0 + * which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + ******************************************************************************/ +package org.eclipse.yasson.internal.jsonstructure; + +import org.eclipse.yasson.internal.properties.MessageKeys; +import org.eclipse.yasson.internal.properties.Messages; + +import javax.json.JsonObject; +import javax.json.JsonValue; +import javax.json.bind.JsonbException; +import javax.json.stream.JsonParser; +import java.util.Iterator; + +/** + * Iterates over {@link JsonObject} managing internal state. + */ +public class JsonObjectIterator extends JsonStructureIterator { + + /** + * Location pointer. + */ + public enum State { + START, + KEY, + VALUE, + END + } + + private final JsonObject jsonObject; + + private final Iterator<String> keyIterator; + + private String currentKey; + + private State state = State.START; + + + JsonObjectIterator(JsonObject jsonObject) { + this.jsonObject = jsonObject; + this.keyIterator = jsonObject.keySet().iterator(); + } + + + private void nextKey() { + if (!keyIterator.hasNext()) { + throw new JsonbException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "Object is empty")); + } + currentKey = keyIterator.next(); + } + + @Override + public JsonParser.Event next() { + switch (state) { + case START: + if (keyIterator.hasNext()) { + nextKey(); + setState(JsonObjectIterator.State.KEY); + return JsonParser.Event.KEY_NAME; + } else { + setState(State.END); + return JsonParser.Event.END_OBJECT; + } + case KEY: + setState(JsonObjectIterator.State.VALUE); + JsonValue value = getValue(); + return getValueEvent(value); + case VALUE: + if (keyIterator.hasNext()) { + nextKey(); + setState(JsonObjectIterator.State.KEY); + return JsonParser.Event.KEY_NAME; + } + setState(State.END); + return JsonParser.Event.END_OBJECT; + default: + throw new JsonbException("Illegal state"); + } + + } + + @Override + public boolean hasNext() { + //From the perspective of JsonParser not finished until END_OBJECT is being read. + return state != State.END; + } + + /** + * {@link JsonValue} for current key. + * @return Current JsonValue. + */ + public JsonValue getValue() { + return jsonObject.get(currentKey); + } + + @Override + String getString() { + if (state == JsonObjectIterator.State.KEY) { + return currentKey; + } + return super.getString(); + } + + @Override + JsonbException createIncompatibleValueError() { + return new JsonbException(Messages.getMessage(MessageKeys.NUMBER_INCOMPATIBLE_VALUE_TYPE_OBJECT, getValue().getValueType(), currentKey)); + } + + private void setState(State state) { + this.state = state; + } + + /** + * Current key this iterator is pointing at. + * @return Current key. + */ + public String getKey() { + return currentKey; + } +}
diff --git a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureBuilder.java b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureBuilder.java new file mode 100644 index 0000000..38c0bd4 --- /dev/null +++ b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureBuilder.java
@@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2019 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 v1.0 and Eclipse Distribution License v. 1.0 + * which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + ******************************************************************************/ +package org.eclipse.yasson.internal.jsonstructure; + +import javax.json.JsonStructure; +import javax.json.JsonValue; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * Grouping interface for {@link javax.json.JsonObject} and {@link javax.json.JsonArray} generation. + */ +abstract class JsonStructureBuilder { + + /** + * Build and get constructed {@link JsonStructure} + * @return JsonStructure result. + */ + abstract JsonStructure build(); + + /** + * Puts another {@link JsonStructure} into current. If current is {@link javax.json.JsonObject} than last written + * key is used. + * @param structure + */ + abstract void put(JsonStructure structure); + + /** + * Write a value into current {@link JsonStructure}. If current is {@link javax.json.JsonObject}, last stored key + * by {@link JsonObjectBuilder#writeKey(String)} is used. + * + * @param value A value to write. + */ + abstract void write(JsonValue value); + + /** + * Write a value into current {@link JsonStructure}. If current is {@link javax.json.JsonObject}, last stored key + * by {@link JsonObjectBuilder#writeKey(String)} is used. + * + * @param value A value to write. + */ + abstract void write(String value); + + /** + * Write a value into current {@link JsonStructure}. If current is {@link javax.json.JsonObject}, last stored key + * by {@link JsonObjectBuilder#writeKey(String)} is used. + * + * @param value A value to write. + */ + abstract void write(BigDecimal value); + + /** + * Write a value into current {@link JsonStructure}. If current is {@link javax.json.JsonObject}, last stored key + * by {@link JsonObjectBuilder#writeKey(String)} is used. + * + * @param value A value to write. + */ + abstract void write(BigInteger value); + + /** + * Write a value into current {@link JsonStructure}. If current is {@link javax.json.JsonObject}, last stored key + * by {@link JsonObjectBuilder#writeKey(String)} is used. + * + * @param value A value to write. + */ + abstract void write(int value); + + /** + * Write a value into current {@link JsonStructure}. If current is {@link javax.json.JsonObject}, last stored key + * by {@link JsonObjectBuilder#writeKey(String)} is used. + * + * @param value A value to write. + */ + abstract void write(long value); + + /** + * Write a value into current {@link JsonStructure}. If current is {@link javax.json.JsonObject}, last stored key + * by {@link JsonObjectBuilder#writeKey(String)} is used. + * + * @param value A value to write. + */ + abstract void write(double value); + + /** + * Write a value into current {@link JsonStructure}. If current is {@link javax.json.JsonObject}, last stored key + * by {@link JsonObjectBuilder#writeKey(String)} is used. + * + * @param value A value to write. + */ + abstract void write(boolean value); + + /** + * Write null into current {@link JsonStructure}. If current is {@link javax.json.JsonObject}, last stored key + * by {@link JsonObjectBuilder#writeKey(String)} is used. + */ + abstract void writeNull(); +}
diff --git a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureIterator.java b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureIterator.java new file mode 100644 index 0000000..8ea3578 --- /dev/null +++ b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureIterator.java
@@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2019 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 v1.0 and Eclipse Distribution License v. 1.0 + * which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + ******************************************************************************/ +package org.eclipse.yasson.internal.jsonstructure; + +import org.eclipse.yasson.internal.properties.MessageKeys; +import org.eclipse.yasson.internal.properties.Messages; + +import javax.json.JsonString; +import javax.json.JsonValue; +import javax.json.bind.JsonbException; +import javax.json.stream.JsonParser; +import java.util.Iterator; + +/** + * Iterates over {@link javax.json.JsonStructure}. + */ +abstract class JsonStructureIterator implements Iterator<JsonParser.Event> { + + /** + * Get current {@link JsonValue}, that the parser is pointing on. + * @return JsonValue result. + */ + abstract JsonValue getValue(); + + /** + * Creates an exception for throwing in case of current value type is not compatible with + * called getter return type. + * + * @return JsonbException with error description. + */ + abstract JsonbException createIncompatibleValueError(); + + /** + * Check the type of current {@link JsonValue} and return a string representing a value. + * @return String value for current JsonValue + */ + String getString() { + JsonValue value = getValue(); + if (value instanceof JsonString) { + return ((JsonString) value).getString(); + } else { + return value.toString(); + } + } + + /** + * Convert {@link JsonValue} type to {@link JsonParser.Event}. + * @param value JsonValue + * @return JsonParser event + */ + JsonParser.Event getValueEvent(JsonValue value) { + switch (value.getValueType()) { + case NUMBER: + return JsonParser.Event.VALUE_NUMBER; + case STRING: + case TRUE: + case FALSE: + return JsonParser.Event.VALUE_STRING; + case OBJECT: + return JsonParser.Event.START_OBJECT; + case ARRAY: + return JsonParser.Event.START_ARRAY; + case NULL: + return JsonParser.Event.VALUE_NULL; + default: + throw new JsonbException(Messages.getMessage(MessageKeys.INTERNAL_ERROR, "unknown json value: " + value.getValueType())); + } + + } +}
diff --git a/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureToParserAdapter.java b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureToParserAdapter.java new file mode 100644 index 0000000..50c4592 --- /dev/null +++ b/src/main/java/org/eclipse/yasson/internal/jsonstructure/JsonStructureToParserAdapter.java
@@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2019 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 v1.0 and Eclipse Distribution License v. 1.0 + * which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + ******************************************************************************/ +package org.eclipse.yasson.internal.jsonstructure; + +import javax.json.JsonArray; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonStructure; +import javax.json.JsonValue; +import javax.json.bind.JsonbException; +import javax.json.stream.JsonLocation; +import javax.json.stream.JsonParser; +import java.math.BigDecimal; +import java.util.Stack; + +/** + * 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 Stack<JsonStructureIterator> iterators = new Stack<>(); + + private final JsonStructure rootStructure; + + public JsonStructureToParserAdapter(JsonStructure structure) { + this.rootStructure = structure; + } + + @Override + public boolean hasNext() { + return iterators.peek().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; + } + } + JsonStructureIterator current = iterators.peek(); + Event next = current.next(); + if (next == Event.START_OBJECT) { + iterators.push(new JsonObjectIterator((JsonObject) iterators.peek().getValue())); + } else if (next == Event.START_ARRAY) { + iterators.push(new JsonArrayIterator((JsonArray) iterators.peek().getValue())); + } else if (next == Event.END_OBJECT || next == Event.END_ARRAY) { + iterators.pop(); + } + return next; + } + + + + @Override + public String getString() { + return iterators.peek().getString(); + } + + @Override + public boolean isIntegralNumber() { + return getJsonNumberValue().isIntegral(); + } + + @Override + public int getInt() { + return getJsonNumberValue().intValueExact(); + } + + @Override + public long getLong() { + return getJsonNumberValue().longValueExact(); + } + + @Override + public BigDecimal getBigDecimal() { + return getJsonNumberValue().bigDecimalValue(); + } + + private JsonNumber getJsonNumberValue() { + JsonStructureIterator iterator = iterators.peek(); + JsonValue value = iterator.getValue(); + if (value.getValueType() != JsonValue.ValueType.NUMBER) { + throw iterator.createIncompatibleValueError(); + } + return (JsonNumber) value; + } + + @Override + public JsonLocation getLocation() { + throw new JsonbException("Operation not supported"); + } + + @Override + public void close() { + //noop + } +}
diff --git a/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java b/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java index 475f8dd..3336dda 100644 --- a/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java +++ b/src/main/java/org/eclipse/yasson/internal/properties/MessageKeys.java
@@ -86,6 +86,8 @@ MULTIPLE_CONSTRUCTOR_PROPERTIES_CREATORS("multipleConstructorPropertiesCreators"), ANNOTATION_NOT_AVAILABLE("annotationNotAvailable"), MISSING_VALUE_PROPERTY_IN_ANNOTATION("missingValuePropertyInAnnotation"), + NUMBER_INCOMPATIBLE_VALUE_TYPE_ARRAY("numberIncompatibleValueTypeArray"), + NUMBER_INCOMPATIBLE_VALUE_TYPE_OBJECT("numberIncompatibleValueTypeObject"), ; /** Message bundle key. */
diff --git a/src/main/resources/yasson-messages.properties b/src/main/resources/yasson-messages.properties index 5ed7eff..59b8a68 100644 --- a/src/main/resources/yasson-messages.properties +++ b/src/main/resources/yasson-messages.properties
@@ -79,4 +79,6 @@ datatypeFactoryCreationFailed=An error occurred while DatatypeFactory creation. multipleConstructorPropertiesCreators=More than one constructor annotated with @ConstructorProperties declared in class {0}. annotationNotAvailable=Annotation {0} is not visible in modules or classpath. Annotation will be ignored. -missingValuePropertyInAnnotation=Missing value property in Annotation {0}. Annotation will be ignored. \ No newline at end of file +missingValuePropertyInAnnotation=Missing value property in Annotation {0}. Annotation will be ignored. +numberIncompatibleValueTypeArray=Value type {0} is not a JsonNumber. +numberIncompatibleValueTypeObject=Value type {0} at key {1} is not a JsonNumber. \ No newline at end of file
diff --git a/src/test/java/org/eclipse/yasson/jsonstructure/InnerPojo.java b/src/test/java/org/eclipse/yasson/jsonstructure/InnerPojo.java new file mode 100644 index 0000000..0a0faa6 --- /dev/null +++ b/src/test/java/org/eclipse/yasson/jsonstructure/InnerPojo.java
@@ -0,0 +1,22 @@ +package org.eclipse.yasson.jsonstructure; + +public final class InnerPojo { + private String innerFirst; + private String innerSecond; + + public String getInnerFirst() { + return innerFirst; + } + + public void setInnerFirst(String innerFirst) { + this.innerFirst = innerFirst; + } + + public String getInnerSecond() { + return innerSecond; + } + + public void setInnerSecond(String innerSecond) { + this.innerSecond = innerSecond; + } +}
diff --git a/src/test/java/org/eclipse/yasson/jsonstructure/InnerPojoDeserializer.java b/src/test/java/org/eclipse/yasson/jsonstructure/InnerPojoDeserializer.java new file mode 100644 index 0000000..dee7b03 --- /dev/null +++ b/src/test/java/org/eclipse/yasson/jsonstructure/InnerPojoDeserializer.java
@@ -0,0 +1,26 @@ +package org.eclipse.yasson.jsonstructure; + +import javax.json.bind.serializer.DeserializationContext; +import javax.json.bind.serializer.JsonbDeserializer; +import javax.json.stream.JsonParser; +import java.lang.reflect.Type; + +public class InnerPojoDeserializer implements JsonbDeserializer<InnerPojo> { + @Override + public InnerPojo deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { + InnerPojo innerPojo = new InnerPojo(); + //KEY first + parser.next(); + //VALUE + parser.next(); + innerPojo.setInnerFirst(parser.getString()); + //KEY second + parser.next(); + //VALUE + parser.next(); + innerPojo.setInnerSecond(parser.getString()); + //END_OBJECT + parser.next(); + return innerPojo; + } +}
diff --git a/src/test/java/org/eclipse/yasson/jsonstructure/InnerPojoSerializer.java b/src/test/java/org/eclipse/yasson/jsonstructure/InnerPojoSerializer.java new file mode 100644 index 0000000..3c21dd4 --- /dev/null +++ b/src/test/java/org/eclipse/yasson/jsonstructure/InnerPojoSerializer.java
@@ -0,0 +1,16 @@ +package org.eclipse.yasson.jsonstructure; + +import javax.json.bind.serializer.JsonbSerializer; +import javax.json.bind.serializer.SerializationContext; +import javax.json.stream.JsonGenerator; + +public class InnerPojoSerializer implements JsonbSerializer<InnerPojo> { + + @Override + public void serialize(InnerPojo obj, JsonGenerator generator, SerializationContext ctx) { + generator.writeStartObject(); + generator.write("first", obj.getInnerFirst()); + generator.write("second", obj.getInnerSecond()); + generator.writeEnd(); + } +}
diff --git a/src/test/java/org/eclipse/yasson/jsonstructure/JsonGeneratorToStructureAdapterTest.java b/src/test/java/org/eclipse/yasson/jsonstructure/JsonGeneratorToStructureAdapterTest.java new file mode 100644 index 0000000..2fc0c18 --- /dev/null +++ b/src/test/java/org/eclipse/yasson/jsonstructure/JsonGeneratorToStructureAdapterTest.java
@@ -0,0 +1,150 @@ +package org.eclipse.yasson.jsonstructure; + +import org.eclipse.yasson.YassonJsonb; +import org.junit.Test; + +import javax.json.JsonArray; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonStructure; +import javax.json.JsonValue; +import javax.json.bind.JsonbBuilder; +import javax.json.bind.JsonbConfig; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class JsonGeneratorToStructureAdapterTest { + + private final YassonJsonb jsonb = (YassonJsonb) JsonbBuilder.create(); + + @Test + public void testSimplePojo() { + Pojo pojo = new Pojo(); + pojo.setBigDecimalProperty(BigDecimal.TEN); + pojo.setLongProperty(10L); + pojo.setStringProperty("String value"); + + JsonObject result = (JsonObject) jsonb.toJsonStructure(pojo); + + assertEquals("String value", getString(result.get("stringProperty"))); + JsonValue bigDecimalProperty = result.get("bigDecimalProperty"); + assertEquals(JsonValue.ValueType.NUMBER, bigDecimalProperty.getValueType()); + assertEquals(BigDecimal.TEN, ((JsonNumber) bigDecimalProperty).bigDecimalValue()); + JsonValue longProperty = result.get("longProperty"); + assertEquals(JsonValue.ValueType.NUMBER, longProperty.getValueType()); + assertEquals(10L, ((JsonNumber) longProperty).longValueExact()); + } + + @Test + public void testInnerObjects() { + Pojo pojo = new Pojo(); + pojo.setBigDecimalProperty(BigDecimal.TEN); + pojo.setLongProperty(10L); + pojo.setStringProperty("String value"); + pojo.setInner(new InnerPojo()); + pojo.getInner().setInnerFirst("First"); + pojo.getInner().setInnerSecond("Second"); + + JsonObject result = (JsonObject) jsonb.toJsonStructure(pojo, Pojo.class); + assertEquals("String value", getString(result.get("stringProperty"))); + JsonValue bigDecimalProperty = result.get("bigDecimalProperty"); + assertTrue(bigDecimalProperty instanceof JsonNumber); + assertEquals(BigDecimal.TEN, ((JsonNumber) bigDecimalProperty).bigDecimalValue()); + JsonValue longProperty = result.get("longProperty"); + assertTrue(longProperty instanceof JsonNumber); + assertEquals(10L, ((JsonNumber) longProperty).longValueExact()); + + JsonValue inner = result.get("inner"); + assertEquals(JsonValue.ValueType.OBJECT, inner.getValueType()); + assertEquals("First", ((JsonObject)inner).getString("innerFirst")); + assertEquals("Second", ((JsonObject)inner).getString("innerSecond")); + + } + + @Test + public void testSimpleJsonArray() { + List<Object> objList = new ArrayList<>(); + objList.add("First"); + objList.add(10L); + objList.add(BigDecimal.ONE); + objList.add(Boolean.TRUE); + objList.add(null); + + JsonArray result = (JsonArray) jsonb.toJsonStructure(objList); + assertEquals("First", result.getString(0)); + assertEquals(10L, result.getJsonNumber(1).longValueExact()); + assertEquals(BigDecimal.ONE, result.getJsonNumber(2).bigDecimalValue()); + assertEquals(Boolean.TRUE, result.getBoolean(3)); + assertEquals(JsonValue.ValueType.NULL, result.get(4).getValueType()); + } + + @Test + public void testJsonArrayInJsonObject() { + Pojo pojo = new Pojo(); + pojo.setStrings(new ArrayList<>()); + pojo.setBigDecimals(new ArrayList<>()); + pojo.setBooleans(new ArrayList<>()); + pojo.getStrings().add("First"); + pojo.getBigDecimals().add(BigDecimal.TEN); + pojo.getBooleans().add(Boolean.TRUE); + + JsonObject result = (JsonObject) jsonb.toJsonStructure(pojo); + assertEquals(JsonValue.ValueType.ARRAY, result.get("strings").getValueType()); + assertEquals(JsonValue.ValueType.ARRAY, result.get("bigDecimals").getValueType()); + assertEquals(JsonValue.ValueType.ARRAY, result.get("booleans").getValueType()); + assertEquals("First", result.getJsonArray("strings").getString(0)); + assertEquals(BigDecimal.TEN, result.getJsonArray("bigDecimals").getJsonNumber(0).bigDecimalValue()); + assertEquals(Boolean.TRUE, result.getJsonArray("booleans").getBoolean(0)); + } + + @Test + public void testNestedJsonArrays() { + List<List<Object>> outer = new ArrayList<>(); + List<Object> inner = new ArrayList<>(); + inner.add("First"); + inner.add(10L); + inner.add(BigDecimal.ONE); + inner.add(Boolean.TRUE); + inner.add(null); + outer.add(inner); + + JsonArray result = (JsonArray) jsonb.toJsonStructure(outer); + assertEquals(JsonValue.ValueType.ARRAY, result.get(0).getValueType()); + JsonArray resultInner = result.getJsonArray(0); + + assertEquals("First", resultInner.getString(0)); + assertEquals(10L, resultInner.getJsonNumber(1).longValueExact()); + assertEquals(BigDecimal.ONE, resultInner.getJsonNumber(2).bigDecimalValue()); + assertEquals(Boolean.TRUE, resultInner.getBoolean(3)); + assertEquals(JsonValue.ValueType.NULL, resultInner.get(4).getValueType()); + } + + @Test + public void testCustomJsonbSerializer() { + Pojo pojo = new Pojo(); + pojo.setInner(new InnerPojo()); + pojo.getInner().setInnerFirst("First value"); + pojo.getInner().setInnerSecond("Second value"); + YassonJsonb jsonb = (YassonJsonb) JsonbBuilder.create(new JsonbConfig().withSerializers(new InnerPojoSerializer())); + JsonStructure result = jsonb.toJsonStructure(pojo); + assertEquals(JsonValue.ValueType.OBJECT, result.getValueType()); + assertEquals(JsonValue.ValueType.OBJECT, ((JsonObject) result).get("inner").getValueType()); + JsonObject inner = (JsonObject) ((JsonObject) result).get("inner"); + assertEquals(JsonValue.ValueType.STRING, inner.get("first").getValueType()); + assertEquals("First value", ((JsonString) inner.get("first")).getString()); + assertEquals(JsonValue.ValueType.STRING, inner.get("second").getValueType()); + assertEquals("Second value", ((JsonString) inner.get("second")).getString()); + } + + private String getString(JsonValue value) { + if (value instanceof JsonString) { + return ((JsonString) value).getString(); + } + return value.toString(); + } +}
diff --git a/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java b/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java new file mode 100644 index 0000000..44e5327 --- /dev/null +++ b/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java
@@ -0,0 +1,266 @@ +package org.eclipse.yasson.jsonstructure; + +import org.eclipse.yasson.TestTypeToken; +import org.eclipse.yasson.YassonJsonb; +import org.junit.Test; + +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.bind.JsonbBuilder; +import javax.json.bind.JsonbConfig; +import javax.json.spi.JsonProvider; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +public class JsonStructureToParserAdapterTest { + + private final YassonJsonb jsonb = (YassonJsonb) JsonbBuilder.create(); + + private final JsonProvider jsonProvider = JsonProvider.provider(); + + @Test + public void testBasicJsonObject() { + JsonObjectBuilder objectBuilder = jsonProvider.createObjectBuilder(); + objectBuilder.add("stringProperty", "value 1"); + objectBuilder.add("bigDecimalProperty", new BigDecimal("1.1")); + objectBuilder.add("longProperty", 10L); + JsonObject jsonObject = objectBuilder.build(); + Pojo result = jsonb.fromJsonStructure(jsonObject, Pojo.class); + assertEquals("value 1", result.getStringProperty()); + assertEquals(new BigDecimal("1.1"), result.getBigDecimalProperty()); + assertEquals(Long.valueOf(10), result.getLongProperty()); + } + + @Test + public void testNullValues() { + JsonObjectBuilder objectBuilder = jsonProvider.createObjectBuilder(); + objectBuilder.addNull("stringProperty"); + objectBuilder.addNull("bigDecimalProperty"); + objectBuilder.add("longProperty", 10L); + JsonObject jsonObject = objectBuilder.build(); + Pojo result = jsonb.fromJsonStructure(jsonObject, Pojo.class); + assertNull(result.getStringProperty()); + assertNull(result.getBigDecimalProperty()); + assertEquals(Long.valueOf(10), result.getLongProperty()); + } + + @Test + public void testInnerJsonObjectWrappedWithProperties() { + JsonObjectBuilder innerBuilder = jsonProvider.createObjectBuilder(); + innerBuilder.add("innerFirst", "Inner value 1"); + innerBuilder.add("innerSecond", "Inner value 2"); + + JsonObjectBuilder objectBuilder = jsonProvider.createObjectBuilder(); + + objectBuilder.add("stringProperty", "value 1"); + objectBuilder.add("inner", innerBuilder.build()); + objectBuilder.add("bigDecimalProperty", new BigDecimal("1.1")); + objectBuilder.add("longProperty", 10L); + JsonObject jsonObject = objectBuilder.build(); + Pojo result = jsonb.fromJsonStructure(jsonObject, Pojo.class); + + assertEquals("value 1", result.getStringProperty()); + assertEquals(new BigDecimal("1.1"), result.getBigDecimalProperty()); + assertEquals(Long.valueOf(10), result.getLongProperty()); + assertEquals("Inner value 1", result.getInner().getInnerFirst()); + assertEquals("Inner value 2", result.getInner().getInnerSecond()); + } + + @Test + public void testInnerJsonObjectAtEndProperty() { + JsonObjectBuilder innerBuilder = jsonProvider.createObjectBuilder(); + innerBuilder.add("innerFirst", "Inner value 1"); + innerBuilder.add("innerSecond", "Inner value 2"); + + JsonObjectBuilder objectBuilder = jsonProvider.createObjectBuilder(); + + objectBuilder.add("stringProperty", "value 1"); + objectBuilder.add("bigDecimalProperty", new BigDecimal("1.1")); + objectBuilder.add("longProperty", 10L); + objectBuilder.add("inner", innerBuilder.build()); + + JsonObject jsonObject = objectBuilder.build(); + Pojo result = jsonb.fromJsonStructure(jsonObject, Pojo.class); + + assertEquals("value 1", result.getStringProperty()); + assertEquals(new BigDecimal("1.1"), result.getBigDecimalProperty()); + assertEquals(Long.valueOf(10), result.getLongProperty()); + assertEquals("Inner value 1", result.getInner().getInnerFirst()); + assertEquals("Inner value 2", result.getInner().getInnerSecond()); + + } + + @Test + public void testEmptyJsonObject() { + JsonObjectBuilder objectBuilder = jsonProvider.createObjectBuilder(); + JsonObject jsonObject = objectBuilder.build(); + Pojo result = jsonb.fromJsonStructure(jsonObject, Pojo.class); + assertNull(result.getStringProperty()); + assertNull(result.getBigDecimalProperty()); + assertNull(result.getLongProperty()); + } + + @Test + public void testEmptyInnerJsonObject() { + JsonObjectBuilder objectBuilder = jsonProvider.createObjectBuilder(); + + JsonObjectBuilder innerBuilder = jsonProvider.createObjectBuilder(); + JsonObject innerObject = innerBuilder.build(); + + objectBuilder.add("inner", innerObject); + + JsonObject jsonObject = objectBuilder.build(); + + Pojo result = jsonb.fromJsonStructure(jsonObject, Pojo.class); + assertNull(result.getStringProperty()); + assertNull(result.getBigDecimalProperty()); + assertNull(result.getLongProperty()); + + assertNotNull(result.getInner()); + assertNull(result.getInner().getInnerFirst()); + assertNull(result.getInner().getInnerSecond()); + } + + @Test + public void testSimpleArray() { + JsonArrayBuilder arrayBuilder = jsonProvider.createArrayBuilder(); + arrayBuilder.add(BigDecimal.TEN).add("String value").addNull(); + JsonArray jsonArray = arrayBuilder.build(); + List result = jsonb.fromJsonStructure(jsonArray, ArrayList.class); + assertEquals(3, result.size()); + assertEquals(BigDecimal.TEN, result.get(0)); + assertEquals("String value", result.get(1)); + assertNull(result.get(2)); + } + + @Test + public void testArraysInsideObject() { + JsonArrayBuilder bigDecBuilder = jsonProvider.createArrayBuilder(); + JsonArrayBuilder strBuilder = jsonProvider.createArrayBuilder(); + JsonArrayBuilder blnBuilder = jsonProvider.createArrayBuilder(); + + bigDecBuilder.add(BigDecimal.TEN); + strBuilder.add("String value 1"); + blnBuilder.add(Boolean.TRUE); + + JsonObjectBuilder pojoBuilder = jsonProvider.createObjectBuilder(); + pojoBuilder.add("strings", strBuilder.build()); + pojoBuilder.add("bigDecimals", bigDecBuilder.build()); + pojoBuilder.add("booleans", blnBuilder.build()); + + JsonObject jsonObject = pojoBuilder.build(); + Pojo pojo = jsonb.fromJsonStructure(jsonObject, Pojo.class); + + assertEquals(1, pojo.getBigDecimals().size()); + assertEquals(1, pojo.getStrings().size()); + assertEquals(1, pojo.getBooleans().size()); + } + + @Test + public void testNestedArrays() { + JsonArrayBuilder arrayBuilder = jsonProvider.createArrayBuilder(); + JsonArrayBuilder innerArrBuilder = jsonProvider.createArrayBuilder(); + innerArrBuilder.add("first").add("second"); + arrayBuilder.add(BigDecimal.TEN); + arrayBuilder.add(innerArrBuilder.build()); + + JsonArray jsonArray = arrayBuilder.build(); + + ArrayList result = jsonb.fromJsonStructure(jsonArray, ArrayList.class); + assertEquals(2, result.size()); + assertEquals(BigDecimal.TEN, result.get(0)); + assertTrue(result.get(1) instanceof List); + List inner = (List) result.get(1); + assertEquals(2, inner.size()); + assertEquals("first", inner.get(0)); + assertEquals("second", inner.get(1)); + } + + @Test + public void testObjectsNestedInArrays() { + JsonObjectBuilder objectBuilder = jsonProvider.createObjectBuilder(); + objectBuilder.add("stringProperty", "value 1"); + objectBuilder.add("bigDecimalProperty", new BigDecimal("1.1")); + objectBuilder.add("longProperty", 10L); + + JsonArrayBuilder innerArrayBuilder = jsonProvider.createArrayBuilder(); + innerArrayBuilder.add("String value 1"); + objectBuilder.add("strings", innerArrayBuilder.build()); + + JsonArrayBuilder arrayBuilder = jsonProvider.createArrayBuilder(); + arrayBuilder.add(objectBuilder.build()); + + JsonArray rootArray = arrayBuilder.build(); + + List<Object> result = jsonb.fromJsonStructure(rootArray, new TestTypeToken<List<Pojo>>(){}.getType()); + assertTrue(result.get(0) instanceof Pojo); + Pojo pojo = (Pojo) result.get(0); + assertNotNull(pojo); + assertEquals("value 1", pojo.getStringProperty()); + assertEquals(new BigDecimal("1.1"), pojo.getBigDecimalProperty()); + assertEquals(Long.valueOf(10), pojo.getLongProperty()); + assertNotNull(pojo.getStrings()); + assertEquals(1, pojo.getStrings().size()); + assertEquals("String value 1", pojo.getStrings().get(0)); + } + + @Test + public void testObjectsNestedInArraysRaw() { + JsonObjectBuilder objectBuilder = jsonProvider.createObjectBuilder(); + objectBuilder.add("stringProperty", "value 1"); + objectBuilder.add("bigDecimalProperty", new BigDecimal("1.1")); + objectBuilder.add("longProperty", 10L); + + JsonArrayBuilder innerArrayBuilder = jsonProvider.createArrayBuilder(); + innerArrayBuilder.add("String value 1"); + + objectBuilder.add("strings", innerArrayBuilder.build()); + + JsonArrayBuilder arrayBuilder = jsonProvider.createArrayBuilder(); + arrayBuilder.add(10L); + arrayBuilder.add(objectBuilder.build()); + arrayBuilder.add("10"); + + JsonArray rootArray = arrayBuilder.build(); + + List<Object> result = jsonb.fromJsonStructure(rootArray, new TestTypeToken<List<Object>>(){}.getType()); + assertEquals(new BigDecimal("10"), result.get(0)); + assertTrue(result.get(1) instanceof Map); + Map pojo = (Map) result.get(1); + assertNotNull(pojo); + assertEquals("value 1", pojo.get("stringProperty")); + assertEquals(new BigDecimal("1.1"), pojo.get("bigDecimalProperty")); + assertEquals(new BigDecimal(10), pojo.get("longProperty")); + assertTrue(pojo.get("strings") instanceof List); + List strings = (List) pojo.get("strings"); + assertNotNull(strings); + assertEquals(1, strings.size()); + assertEquals("String value 1", strings.get(0)); + } + + + @Test + public void testCustomJsonbDeserializer() { + JsonObjectBuilder outerBuilder = jsonProvider.createObjectBuilder(); + JsonObjectBuilder innerBuilder = jsonProvider.createObjectBuilder(); + innerBuilder.add("first", "String value 1"); + innerBuilder.add("second", "String value 2"); + outerBuilder.add("inner", innerBuilder.build()); + JsonObject object = outerBuilder.build(); + + YassonJsonb jsonb = (YassonJsonb) JsonbBuilder.create(new JsonbConfig().withDeserializers(new InnerPojoDeserializer())); + Pojo result = jsonb.fromJsonStructure(object, Pojo.class); + assertNotNull(result.getInner()); + assertEquals("String value 1", result.getInner().getInnerFirst()); + assertEquals("String value 2", result.getInner().getInnerSecond()); + } +}
diff --git a/src/test/java/org/eclipse/yasson/jsonstructure/Pojo.java b/src/test/java/org/eclipse/yasson/jsonstructure/Pojo.java new file mode 100644 index 0000000..f5e2e7d --- /dev/null +++ b/src/test/java/org/eclipse/yasson/jsonstructure/Pojo.java
@@ -0,0 +1,72 @@ +package org.eclipse.yasson.jsonstructure; + +import java.math.BigDecimal; +import java.util.List; + +public final class Pojo { + + private String stringProperty; + private InnerPojo inner; + private BigDecimal bigDecimalProperty; + private Long longProperty; + + private List<String> strings; + private List<BigDecimal> bigDecimals; + private List<Boolean> booleans; + + public String getStringProperty() { + return stringProperty; + } + + public void setStringProperty(String stringProperty) { + this.stringProperty = stringProperty; + } + + public BigDecimal getBigDecimalProperty() { + return bigDecimalProperty; + } + + public void setBigDecimalProperty(BigDecimal bigDecimalProperty) { + this.bigDecimalProperty = bigDecimalProperty; + } + + public InnerPojo getInner() { + return inner; + } + + public void setInner(InnerPojo inner) { + this.inner = inner; + } + + public Long getLongProperty() { + return longProperty; + } + + public void setLongProperty(Long longProperty) { + this.longProperty = longProperty; + } + + public List<String> getStrings() { + return strings; + } + + public void setStrings(List<String> strings) { + this.strings = strings; + } + + public List<BigDecimal> getBigDecimals() { + return bigDecimals; + } + + public void setBigDecimals(List<BigDecimal> bigDecimals) { + this.bigDecimals = bigDecimals; + } + + public List<Boolean> getBooleans() { + return booleans; + } + + public void setBooleans(List<Boolean> booleans) { + this.booleans = booleans; + } +}