JsonSerializable.java
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.azure.cosmos.implementation; import com.azure.cosmos.models.CompositePath; import com.azure.cosmos.models.ConflictResolutionPolicy; import com.azure.cosmos.models.ExcludedPath; import com.azure.cosmos.models.IncludedPath; import com.azure.cosmos.models.IndexingPolicy; import com.azure.cosmos.models.ModelBridgeInternal; import com.azure.cosmos.models.PartitionKeyDefinition; import com.azure.cosmos.models.SpatialSpec; import com.azure.cosmos.models.SqlParameter; import com.azure.cosmos.models.SqlQuerySpec; import com.azure.cosmos.models.UniqueKey; import com.azure.cosmos.models.UniqueKeyPolicy; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.util.annotation.Nullable; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; /** * Represents a base resource that can be serialized to JSON in the Azure Cosmos DB database service. */ public class JsonSerializable {  private static final ObjectMapper OBJECT_MAPPER = Utils.getSimpleObjectMapper();  private static final Logger LOGGER = LoggerFactory.getLogger(JsonSerializable.class);  transient ObjectNode propertyBag = null; private ObjectMapper om;  public JsonSerializable() {  this.propertyBag = OBJECT_MAPPER.createObjectNode();  } /** * Constructor. * * @param jsonString the json string that represents the JsonSerializable. * @param objectMapper the custom object mapper */  protected JsonSerializable(String jsonString, ObjectMapper objectMapper) { // TODO: Made package private due to #153. #171 adding custom serialization options back.  this.propertyBag = fromJson(jsonString);  this.om = objectMapper;  } /** * Constructor. * * @param jsonString the json string that represents the JsonSerializable. */  public JsonSerializable(String jsonString) {  this.propertyBag = fromJson(jsonString);  } /** * Constructor. * * @param objectNode the {@link ObjectNode} that represent the {@link JsonSerializable} */  public JsonSerializable(ObjectNode objectNode) {  this.propertyBag = objectNode;  }  protected JsonSerializable(ByteBuffer byteBuffer) {  this.propertyBag = fromJson(byteBuffer);  }  protected JsonSerializable(byte[] bytes) {  this.propertyBag = fromJson(bytes);  } private static void checkForValidPOJO(Class<?> c) {  if (c.isAnonymousClass() || c.isLocalClass()) {  throw new IllegalArgumentException(  String.format("%s can't be an anonymous or local class.", c.getName())); }  if (c.isMemberClass() && !Modifier.isStatic(c.getModifiers())) {  throw new IllegalArgumentException(  String.format("%s must be static if it's a member class.", c.getName())); }  } public static Object getValue(JsonNode value) {  if (value.isValueNode()) {  switch (value.getNodeType()) { case BOOLEAN:  return value.asBoolean(); case NUMBER:  if (value.isInt()) {  return value.asInt();  } else if (value.isLong()) {  return value.asLong();  } else if (value.isDouble()) {  return value.asDouble(); } else {  return value; } case STRING:  return value.asText(); default:  return value; } }  return value; } private ObjectMapper getMapper() { // TODO: Made package private due to #153. #171 adding custom serialization options back.  if (this.om != null) {  return this.om; }  return OBJECT_MAPPER; } void setMapper(ObjectMapper om) {  this.om = om;  } @JsonIgnore public Logger getLogger() {  return LOGGER; } public void populatePropertyBag() {  } /** * Returns the propertybag(JsonNode) in a hashMap * * @return the HashMap. */ @SuppressWarnings("unchecked") public Map<String, Object> getMap() {  return getMapper().convertValue(this.propertyBag, HashMap.class); } @SuppressWarnings("unchecked") public <T> Map<String, T> getMap(String propertyKey) {  if (this.propertyBag.has(propertyKey)) {  Object value = this.get(propertyKey);  return (Map<String, T>) getMapper().convertValue(value, HashMap.class); }  return null; } /** * Checks whether a property exists. * * @param propertyName the property to look up. * @return true if the property exists. */ public boolean has(String propertyName) {  return this.propertyBag.has(propertyName); } /** * Removes a value by propertyName. * * @param propertyName the property to remove. */ public void remove(String propertyName) {  this.propertyBag.remove(propertyName);  } /** * Sets the value of a property. * * @param <T> the type of the object. * @param propertyName the property to set. * @param value the value of the property. */ @SuppressWarnings({"unchecked", "rawtypes"}) public <T> void set(String propertyName, T value) {  if (value == null) { // Sets null.  this.propertyBag.putNull(propertyName);  } else if (value instanceof Collection) { // Collection.  ArrayNode jsonArray = propertyBag.arrayNode();  this.internalSetCollection(propertyName, (Collection) value, jsonArray);  this.propertyBag.set(propertyName, jsonArray);  } else if (value instanceof JsonNode) {  this.propertyBag.set(propertyName, (JsonNode) value);  } else if (value instanceof JsonSerializable) { // JsonSerializable  JsonSerializable castedValue = (JsonSerializable) value;  castedValue.populatePropertyBag();  this.propertyBag.set(propertyName, castedValue.propertyBag);  } else if (containsJsonSerializable(value.getClass())) {  ModelBridgeInternal.populatePropertyBag(value);  this.propertyBag.set(propertyName, ModelBridgeInternal.getJsonSerializable(value).propertyBag); } else { // POJO, ObjectNode, number (includes int, float, double etc), boolean, // and string.  this.propertyBag.set(propertyName, getMapper().valueToTree(value)); }  } @SuppressWarnings({"unchecked", "rawtypes"}) private <T> void internalSetCollection(String propertyName, Collection<T> collection, ArrayNode targetArray) {  for (T childValue : collection) {  if (childValue == null) { // Sets null.  targetArray.addNull();  } else if (childValue instanceof Collection) { // When T is also a Collection, use recursion.  ArrayNode childArray = targetArray.addArray();  this.internalSetCollection(propertyName, (Collection) childValue, childArray);  } else if (childValue instanceof JsonNode) {  targetArray.add((JsonNode) childValue);  } else if (childValue instanceof JsonSerializable) { // JsonSerializable  JsonSerializable castedValue = (JsonSerializable) childValue;  castedValue.populatePropertyBag();  targetArray.add(castedValue.propertyBag != null ? castedValue.propertyBag  : this.getMapper().createObjectNode());  } else if (containsJsonSerializable(childValue.getClass())) {  ModelBridgeInternal.populatePropertyBag(childValue);  targetArray.add(ModelBridgeInternal.getJsonSerializable(childValue).propertyBag != null ?  ModelBridgeInternal.getJsonSerializable(childValue).propertyBag : this.getMapper().createObjectNode()); } else { // POJO, JsonNode, NUMBER (includes Int, Float, Double etc), // Boolean, and STRING.  targetArray.add(this.getMapper().valueToTree(childValue)); }  }  } /** * Gets a property value as Object. * * @param propertyName the property to get. * @return the value of the property. */ public Object get(String propertyName) {  if (this.has(propertyName) && this.propertyBag.hasNonNull(propertyName)) {  return getValue(this.propertyBag.get(propertyName)); } else {  return null; } } /** * Gets a string value. * * @param propertyName the property to get. * @return the string value. */ public String getString(String propertyName) {  if (this.has(propertyName) && this.propertyBag.hasNonNull(propertyName)) {  return this.propertyBag.get(propertyName).asText(); } else {  return null; } } /** * Gets a boolean value. * * @param propertyName the property to get. * @return the boolean value. */ // The method returning Boolean can be invoked as though it returned a value of type boolean, // and the compiler will insert automatic unboxing of the Boolean value. If a null value is // returned, this will result in a NPE. @Nullable is used indicate that returning null is permitted. @Nullable public Boolean getBoolean(String propertyName) {  if (this.has(propertyName) && this.propertyBag.hasNonNull(propertyName)) {  return this.propertyBag.get(propertyName).asBoolean(); } else {  return null; } } /** * Gets an integer value. * * @param propertyName the property to get. * @return the boolean value */ public Integer getInt(String propertyName) {  if (this.has(propertyName) && this.propertyBag.hasNonNull(propertyName)) {  return this.propertyBag.get(propertyName).asInt(); } else {  return null; } } /** * Gets a long value. * * @param propertyName the property to get. * @return the long value */ protected Long getLong(String propertyName) {  if (this.has(propertyName) && this.propertyBag.hasNonNull(propertyName)) {  return this.propertyBag.get(propertyName).asLong(); } else {  return null; } } /** * Gets a double value. * * @param propertyName the property to get. * @return the double value. */ public Double getDouble(String propertyName) {  if (this.has(propertyName) && this.propertyBag.hasNonNull(propertyName)) {  return this.propertyBag.get(propertyName).asDouble(); } else {  return null; } } /** * Gets an object value. * * @param <T> the type of the object. * @param propertyName the property to get. * @param c the class of the object. If c is a POJO class, it must be a member (and not an anonymous or local) * and a static one. * @param convertFromCamelCase boolean indicating if String should be converted from camel case to upper case * separated by underscore, * before converting to required class. * @return the object value. * @throws IllegalStateException thrown if an error occurs */ @SuppressWarnings("unchecked") // Implicit or explicit cast to T is done only after checking values are assignable from Class<T>. public <T> T getObject(String propertyName, Class<T> c, boolean... convertFromCamelCase) {  if (this.propertyBag.has(propertyName) && this.propertyBag.hasNonNull(propertyName)) {  JsonNode jsonObj = propertyBag.get(propertyName);  if (Number.class.isAssignableFrom(c) || String.class.isAssignableFrom(c)  || Boolean.class.isAssignableFrom(c) || Object.class == c) { // NUMBER, STRING, Boolean  return c.cast(getValue(jsonObj));  } else if (Enum.class.isAssignableFrom(c)) { try {  String value = String.class.cast(getValue(jsonObj));  value = convertFromCamelCase.length > 0 && convertFromCamelCase[0]  ? Strings.fromCamelCaseToUpperCase(value) : value;  return c.cast(c.getMethod("valueOf", String.class).invoke(null, value));  } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {  throw new IllegalStateException("Failed to create enum.", e); }  } else if (JsonSerializable.class.isAssignableFrom(c)) {  return (T) ModelBridgeInternal.instantiateJsonSerializable((ObjectNode) jsonObj, c);  } else if (containsJsonSerializable(c)) {  return ModelBridgeInternal.instantiateByObjectNode((ObjectNode) jsonObj, c); } else { // POJO  JsonSerializable.checkForValidPOJO(c); try {  return this.getMapper().treeToValue(jsonObj, c);  } catch (IOException e) {  throw new IllegalStateException("Failed to get POJO.", e); } } }  return null; } /** * Gets an object List. * * @param <T> the type of the objects in the List. * @param propertyName the property to get * @param c the class of the object. If c is a POJO class, it must be a member (and not an anonymous or local) * and a static one. * @param convertFromCamelCase boolean indicating if String should be converted from camel case to upper case * separated by underscore, * before converting to required class. * @return the object collection. * @throws IllegalStateException thrown if an error occurs */ @SuppressWarnings("unchecked") // Implicit or explicit cast to T is done only after checking values are assignable from Class<T>. public <T> List<T> getList(String propertyName, Class<T> c, boolean... convertFromCamelCase) {  if (this.propertyBag.has(propertyName) && this.propertyBag.hasNonNull(propertyName)) {  JsonNode jsonArray = this.propertyBag.get(propertyName);  ArrayList<T> result = new ArrayList<T>();  boolean isBaseClass = false;  boolean isEnumClass = false;  boolean isJsonSerializable = false;  boolean containsJsonSerializable = false; // Check once.  if (Number.class.isAssignableFrom(c) || String.class.isAssignableFrom(c)  || Boolean.class.isAssignableFrom(c) || Object.class == c) {  isBaseClass = true;  } else if (Enum.class.isAssignableFrom(c)) {  isEnumClass = true;  } else if (JsonSerializable.class.isAssignableFrom(c)) {  isJsonSerializable = true;  } else if (containsJsonSerializable(c)) {  containsJsonSerializable = true; } else {  JsonSerializable.checkForValidPOJO(c); }  for (JsonNode n : jsonArray) {  if (isBaseClass) { // NUMBER, STRING, Boolean  result.add(c.cast(getValue(n)));  } else if (isEnumClass) { try {  String value = String.class.cast(getValue(n));  value = convertFromCamelCase.length > 0 && convertFromCamelCase[0]  ? Strings.fromCamelCaseToUpperCase(value) : value;  result.add(c.cast(c.getMethod("valueOf", String.class).invoke(null, value)));  } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {  throw new IllegalStateException("Failed to create enum.", e);  }  } else if (isJsonSerializable) { // JsonSerializable  T t = (T) ModelBridgeInternal.instantiateJsonSerializable((ObjectNode) n, c);  result.add(t);  } else if (containsJsonSerializable) {  T t = ModelBridgeInternal.instantiateByObjectNode((ObjectNode) n, c);  result.add(t);  } else { // POJO try {  result.add(this.getMapper().treeToValue(n, c));  } catch (IOException e) {  throw new IllegalStateException("Failed to get POJO.", e);  } }  }  return result; }  return null; } /** * Gets an object collection. * * @param <T> the type of the objects in the collection. * @param propertyName the property to get * @param c the class of the object. If c is a POJO class, it must be a member (and not an anonymous or local) * and a static one. * @param convertFromCamelCase boolean indicating if String should be converted from camel case to upper case * separated by underscore, * before converting to required class. * @return the object collection. */ public <T> Collection<T> getCollection(String propertyName, Class<T> c, boolean... convertFromCamelCase) {  return getList(propertyName, c, convertFromCamelCase); } /** * Gets a ObjectNode. * * @param propertyName the property to get. * @return the ObjectNode. */ public ObjectNode getObject(String propertyName) {  if (this.propertyBag.has(propertyName) && this.propertyBag.hasNonNull(propertyName)) {  return (ObjectNode) this.propertyBag.get(propertyName); }  return null; } /** * Gets a ObjectNode collection. * * @param propertyName the property to get. * @return the ObjectNode collection. */ Collection<ObjectNode> getCollection(String propertyName) {  Collection<ObjectNode> result = null;  if (this.propertyBag.has(propertyName) && this.propertyBag.hasNonNull(propertyName)) {  result = new ArrayList<ObjectNode>();  for (JsonNode n : this.propertyBag.findValues(propertyName)) {  result.add((ObjectNode) n);  } }  return result; } /** * Gets the value of a property identified by an array of property names that forms the path. * * @param propertyNames that form the path to the property to get. * @return the value of the property. */ public Object getObjectByPath(List<String> propertyNames) {  ObjectNode propBag = this.propertyBag;  JsonNode value = null;  String propertyName = null;  int matchedProperties = 0;  Iterator<String> iterator = propertyNames.iterator();  if (iterator.hasNext()) { do {  propertyName = iterator.next();  if (propBag.has(propertyName)) {  matchedProperties++;  value = propBag.get(propertyName);  if (!value.isObject()) {  break; }  propBag = (ObjectNode) value; } else { break; }  } while (iterator.hasNext());  if (value != null && matchedProperties == propertyNames.size()) {  return getValue(value); } }  return null; } private ObjectNode fromJson(byte[] bytes) { try {  return (ObjectNode) getMapper().readTree(bytes);  } catch (IOException e) {  throw new IllegalArgumentException(  String.format("Unable to parse JSON %s", Arrays.toString(bytes)), e); } } private ObjectNode fromJson(String json) { try {  return (ObjectNode) getMapper().readTree(json);  } catch (IOException e) {  throw new IllegalArgumentException(  String.format("Unable to parse JSON %s", json), e); } } private ObjectNode fromJson(ByteBuffer json) { try {  return (ObjectNode) getMapper().readTree(new ByteBufferBackedInputStream(json));  } catch (IOException e) {  throw new IllegalArgumentException("Unable to parse JSON from ByteBuffer", e); } } /** * Serialize json to byte buffer byte buffer. * * @return the byte buffer */ public ByteBuffer serializeJsonToByteBuffer() {  this.populatePropertyBag();  return Utils.serializeJsonToByteBuffer(getMapper(), propertyBag); } public ByteBuffer serializeJsonToByteBuffer(ObjectMapper objectMapper) {  this.populatePropertyBag();  return Utils.serializeJsonToByteBuffer(objectMapper, propertyBag); } private String toJson(Object object) { try {  return getMapper().writeValueAsString(object);  } catch (JsonProcessingException e) {  throw new IllegalStateException("Unable to convert JSON to STRING", e); } } private String toPrettyJson(Object object) { try {  return getMapper().writerWithDefaultPrettyPrinter().writeValueAsString(object);  } catch (JsonProcessingException e) {  throw new IllegalStateException("Unable to convert JSON to STRING", e); } } /** * Converts to an Object (only POJOs and JsonNode are supported). * * @param <T> the type of the object. * @param c the class of the object, either a POJO class or JsonNode. If c is a POJO class, it must be a member * (and not an anonymous or local) and a static one. * @return the POJO. * @throws IllegalArgumentException thrown if an error occurs * @throws IllegalStateException thrown when objectmapper is unable to read tree */ @SuppressWarnings("unchecked") // Implicit or explicit cast to T is done after checking values are assignable from Class<T>. public <T> T toObject(Class<T> c) { // TODO: We have to remove this if we do not want to support InternalObjectNode anymore, and change all the // tests accordingly  if (InternalObjectNode.class.isAssignableFrom(c)) {  return (T) new InternalObjectNode(this.propertyBag); }  if (JsonSerializable.class.isAssignableFrom(c)  || String.class.isAssignableFrom(c)  || Number.class.isAssignableFrom(c)  || Boolean.class.isAssignableFrom(c)  || containsJsonSerializable(c)) {  return c.cast(this.get(Constants.Properties.VALUE)); }  if (List.class.isAssignableFrom(c)) {  Object o = this.get(Constants.Properties.VALUE); try {  return this.getMapper().readValue(o.toString(), c);  } catch (IOException e) {  throw new IllegalStateException("Failed to convert to collection.", e); } }  if (JsonNode.class.isAssignableFrom(c) || ObjectNode.class.isAssignableFrom(c)) { // JsonNode  if (JsonNode.class != c) {  if (ObjectNode.class != c) {  throw new IllegalArgumentException( "We support JsonNode but not its sub-classes."); } }  return c.cast(this.propertyBag); } else { // POJO  JsonSerializable.checkForValidPOJO(c); try {  return this.getMapper().treeToValue(propertyBag, c);  } catch (IOException e) {  throw new IllegalStateException("Failed to get POJO.", e); } } } /** * Converts to a JSON string. * * @return the JSON string. */ public String toJson() {  return this.toJson(SerializationFormattingPolicy.NONE); } /** * Converts to a JSON string. * * @param formattingPolicy the formatting policy to be used. * @return the JSON string. */ protected String toJson(SerializationFormattingPolicy formattingPolicy) {  this.populatePropertyBag();  if (SerializationFormattingPolicy.INDENTED.equals(formattingPolicy)) {  return toPrettyJson(propertyBag); } else {  return toJson(propertyBag); } } /** * Gets Simple STRING representation of property bag. * <p> * For proper conversion to json and inclusion of the default values * use {@link #toJson()}. * * @return string representation of property bag. */ public String toString() {  return toJson(propertyBag); } public ObjectNode getPropertyBag() {  return this.propertyBag; } <T> boolean containsJsonSerializable(Class<T> c) {  return CompositePath.class.equals(c)  || ConflictResolutionPolicy.class.equals(c)  || ExcludedPath.class.equals(c)  || IncludedPath.class.equals(c)  || IndexingPolicy.class.equals(c)  || PartitionKeyDefinition.class.equals(c)  || SpatialSpec.class.equals(c)  || SqlParameter.class.equals(c)  || SqlQuerySpec.class.equals(c)  || UniqueKey.class.equals(c)  || UniqueKeyPolicy.class.equals(c); } @Override public boolean equals(Object o) {  if (this == o) {  return true; }  if (o == null || getClass() != o.getClass()) {  return false; }  JsonSerializable that = (JsonSerializable) o;  return Objects.equals(propertyBag, that.propertyBag); } @Override public int hashCode() {  return Objects.hash(propertyBag); } } 

ncG1vNJzZmiZqqq%2Fpr%2FDpJirrJmbrqTA0meZpaeSY7CwvsRnrqKmlKTEtHrNnqtomaqqv6Z50p2iZp6fp3qrrdWaZq2do6l6pLvVnqman5VkrrvB0Z5knKejory0e8KopGeZqqq%2FpnrCqKqmp6Njtq68y56knqaklsGqu81ogaynnoiys7XApaCzmZKhsm%2B2wK%2BYZ6Ckork%3D