/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch B.V. licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.client.json; import jakarta.json.JsonNumber; import jakarta.json.JsonValue; import jakarta.json.stream.JsonParser; import jakarta.json.stream.JsonParser.Event; import jakarta.json.stream.JsonParsingException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Base class for {@link JsonpDeserializer} implementations that accept a set of JSON events known at instanciation time. */ public abstract class JsonpDeserializerBase implements JsonpDeserializer { private final EnumSet acceptedEvents; private final EnumSet nativeEvents; protected JsonpDeserializerBase(EnumSet acceptedEvents) { this(acceptedEvents, acceptedEvents); } protected JsonpDeserializerBase(EnumSet acceptedEvents, EnumSet nativeEvents) { this.acceptedEvents = acceptedEvents; this.nativeEvents = nativeEvents; } /** Combines accepted events from a number of deserializers */ protected static EnumSet allAcceptedEvents(JsonpDeserializer... deserializers) { EnumSet result = EnumSet.noneOf(Event.class); for (JsonpDeserializer deserializer: deserializers) { EnumSet set = deserializer.acceptedEvents(); // Disabled for now. Only happens with the experimental Union2 and is caused by string and number // parsers leniency. Need to be replaced with a check on a preferred event type. //if (!Collections.disjoint(result, set)) { // throw new IllegalArgumentException("Deserializer accepted events are not disjoint"); //} result.addAll(set); } return result; } @Override public EnumSet nativeEvents() { return nativeEvents; } /** * The JSON events this deserializer accepts as a starting point */ public final EnumSet acceptedEvents() { return acceptedEvents; } /** * Convenience method for {@code acceptedEvents.contains(event)} */ public final boolean accepts(Event event) { return acceptedEvents.contains(event); } //--------------------------------------------------------------------------------------------- //----- Builtin types static final JsonpDeserializer STRING = // String parsing is lenient and accepts any other primitive type new JsonpDeserializerBase(EnumSet.of( Event.KEY_NAME, Event.VALUE_STRING, Event.VALUE_NUMBER, Event.VALUE_FALSE, Event.VALUE_TRUE ), EnumSet.of(Event.VALUE_STRING) ) { @Override public String deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_TRUE) { return "true"; } if (event == Event.VALUE_FALSE) { return "false"; } return parser.getString(); // also accepts numbers } }; static final JsonpDeserializer INTEGER = new JsonpDeserializerBase( EnumSet.of(Event.VALUE_NUMBER, Event.VALUE_STRING), EnumSet.of(Event.VALUE_NUMBER) ) { @Override public Integer deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_STRING) { return Integer.valueOf(parser.getString()); } return parser.getInt(); } }; static final JsonpDeserializer BOOLEAN = new JsonpDeserializerBase( EnumSet.of(Event.VALUE_FALSE, Event.VALUE_TRUE, Event.VALUE_STRING), EnumSet.of(Event.VALUE_FALSE, Event.VALUE_TRUE) ) { @Override public Boolean deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_STRING) { return Boolean.parseBoolean(parser.getString()); } else { return event == Event.VALUE_TRUE; } } }; static final JsonpDeserializer LONG = new JsonpDeserializerBase( EnumSet.of(Event.VALUE_NUMBER, Event.VALUE_STRING), EnumSet.of(Event.VALUE_NUMBER) ) { @Override public Long deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_STRING) { return Long.valueOf(parser.getString()); } return parser.getLong(); } }; static final JsonpDeserializer FLOAT = new JsonpDeserializerBase( EnumSet.of(Event.VALUE_NUMBER, Event.VALUE_STRING), EnumSet.of(Event.VALUE_NUMBER) ) { @Override public Float deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_STRING) { return Float.valueOf(parser.getString()); } return parser.getBigDecimal().floatValue(); } }; static final JsonpDeserializer DOUBLE = new JsonpDeserializerBase( EnumSet.of(Event.VALUE_NUMBER, Event.VALUE_STRING), EnumSet.of(Event.VALUE_NUMBER) ) { @Override public Double deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_STRING) { return Double.valueOf(parser.getString()); } return parser.getBigDecimal().doubleValue(); } }; static final class DoubleOrNullDeserializer extends JsonpDeserializerBase { static final EnumSet nativeEvents = EnumSet.of(Event.VALUE_NUMBER, Event.VALUE_NULL); static final EnumSet acceptedEvents = EnumSet.of(Event.VALUE_STRING, Event.VALUE_NUMBER, Event.VALUE_NULL); private final double defaultValue; DoubleOrNullDeserializer(double defaultValue) { super(acceptedEvents, nativeEvents); this.defaultValue = defaultValue; } @Override public Double deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_NULL) { return defaultValue; } if (event == Event.VALUE_STRING) { return Double.valueOf(parser.getString()); } return parser.getBigDecimal().doubleValue(); } } static final class IntOrNullDeserializer extends JsonpDeserializerBase { static final EnumSet nativeEvents = EnumSet.of(Event.VALUE_NUMBER, Event.VALUE_NULL); static final EnumSet acceptedEvents = EnumSet.of(Event.VALUE_STRING, Event.VALUE_NUMBER, Event.VALUE_NULL); private final int defaultValue; IntOrNullDeserializer(int defaultValue) { super(acceptedEvents, nativeEvents); this.defaultValue = defaultValue; } @Override public Integer deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_NULL) { return defaultValue; } if (event == Event.VALUE_STRING) { return Integer.valueOf(parser.getString()); } return parser.getInt(); } } static final class StringOrNullDeserializer extends JsonpDeserializerBase { static final EnumSet nativeEvents = EnumSet.of(Event.VALUE_STRING, Event.VALUE_NULL); static final EnumSet acceptedEvents = EnumSet.of(Event.KEY_NAME, Event.VALUE_STRING, Event.VALUE_NUMBER, Event.VALUE_FALSE, Event.VALUE_TRUE, Event.VALUE_NULL); StringOrNullDeserializer() { super(acceptedEvents, nativeEvents); } @Override public String deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_NULL) { return null; } if (event == Event.VALUE_TRUE) { return "true"; } if (event == Event.VALUE_FALSE) { return "false"; } return parser.getString(); } } static final JsonpDeserializer DOUBLE_OR_NAN = new JsonpDeserializerBase( EnumSet.of(Event.VALUE_NUMBER, Event.VALUE_STRING, Event.VALUE_NULL), EnumSet.of(Event.VALUE_NUMBER, Event.VALUE_NULL) ) { @Override public Double deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_NULL) { return Double.NaN; } if (event == Event.VALUE_STRING) { return Double.valueOf(parser.getString()); } return parser.getBigDecimal().doubleValue(); } }; static final JsonpDeserializer NUMBER = new JsonpDeserializerBase( EnumSet.of(Event.VALUE_NUMBER, Event.VALUE_STRING), EnumSet.of(Event.VALUE_NUMBER) ) { @Override public Number deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.VALUE_STRING) { return Double.valueOf(parser.getString()); } return ((JsonNumber)parser.getValue()).numberValue(); } }; static final JsonpDeserializer JSON_VALUE = new JsonpDeserializerBase( EnumSet.allOf(Event.class) ) { @Override public JsonValue deserialize(JsonParser parser, JsonpMapper mapper, Event event) { return parser.getValue(); } }; static final JsonpDeserializer VOID = new JsonpDeserializerBase( EnumSet.noneOf(Event.class) ) { @Override public Void deserialize(JsonParser parser, JsonpMapper mapper) { throw new JsonParsingException("Void types should not have any value", parser.getLocation()); } @Override public Void deserialize(JsonParser parser, JsonpMapper mapper, Event event) { return deserialize(parser, mapper); } }; //----- Collections static class ArrayDeserializer implements JsonpDeserializer> { private final JsonpDeserializer itemDeserializer; private EnumSet acceptedEvents; private static final EnumSet nativeEvents = EnumSet.of(Event.START_ARRAY); protected ArrayDeserializer(JsonpDeserializer itemDeserializer) { this.itemDeserializer = itemDeserializer; } @Override public EnumSet nativeEvents() { return nativeEvents; } @Override public EnumSet acceptedEvents() { // Accepted events is computed lazily // no need for double-checked lock, we don't care about computing it several times if (acceptedEvents == null) { acceptedEvents = EnumSet.of(Event.START_ARRAY); acceptedEvents.addAll(itemDeserializer.acceptedEvents()); } return acceptedEvents; } @Override public List deserialize(JsonParser parser, JsonpMapper mapper, Event event) { if (event == Event.START_ARRAY) { List result = new ArrayList<>(); while ((event = parser.next()) != Event.END_ARRAY) { JsonpUtils.ensureAccepts(itemDeserializer, parser, event); result.add(itemDeserializer.deserialize(parser, mapper, event)); } return result; } else { // Single-value mode JsonpUtils.ensureAccepts(itemDeserializer, parser, event); return Collections.singletonList(itemDeserializer.deserialize(parser, mapper, event)); } } } static class StringMapDeserializer extends JsonpDeserializerBase> { private final JsonpDeserializer itemDeserializer; protected StringMapDeserializer(JsonpDeserializer itemDeserializer) { super(EnumSet.of(Event.START_OBJECT)); this.itemDeserializer = itemDeserializer; } @Override public Map deserialize(JsonParser parser, JsonpMapper mapper, Event event) { Map result = new HashMap<>(); while ((event = parser.next()) != Event.END_OBJECT) { JsonpUtils.expectEvent(parser, Event.KEY_NAME, event); String key = parser.getString(); T value = itemDeserializer.deserialize(parser, mapper); result.put(key, value); } return result; } } static class EnumMapDeserializer extends JsonpDeserializerBase> { private final JsonpDeserializer keyDeserializer; private final JsonpDeserializer valueDeserializer; protected EnumMapDeserializer(JsonpDeserializer keyDeserializer, JsonpDeserializer valueDeserializer) { super(EnumSet.of(Event.START_OBJECT)); this.keyDeserializer = keyDeserializer; this.valueDeserializer = valueDeserializer; } @Override public Map deserialize(JsonParser parser, JsonpMapper mapper, Event event) { Map result = new HashMap<>(); while ((event = parser.next()) != Event.END_OBJECT) { JsonpUtils.expectEvent(parser, Event.KEY_NAME, event); K key = keyDeserializer.deserialize(parser, mapper, event); V value = valueDeserializer.deserialize(parser, mapper); result.put(key, value); } return result; } } }