2424import java .util .Map .Entry ;
2525import java .util .Optional ;
2626import java .util .Set ;
27- import java .util .function .Function ;
27+ import java .util .function .UnaryOperator ;
2828import org .bson .json .JsonMode ;
2929import org .bson .json .JsonWriterSettings ;
3030import org .hypertrace .core .documentstore .Document ;
3434public final class MongoUtils {
3535 public static final String FIELD_SEPARATOR = "." ;
3636 public static final String PREFIX = "$" ;
37+ private static final String LITERAL = PREFIX + "literal" ;
3738 private static final String UNSUPPORTED_OPERATION = "No MongoDB support available for: '%s'" ;
3839 private static final ObjectMapper MAPPER = new ObjectMapper ();
3940 private static final Map <ReturnDocumentType , ReturnDocument > RETURN_DOCUMENT_MAP =
@@ -66,7 +67,8 @@ public static String decodeKey(final String key) {
6667 public static String sanitizeJsonString (final String jsonString ) throws JsonProcessingException {
6768 final JsonNode jsonNode = MAPPER .readTree (jsonString );
6869 // escape "." and "$" in field names since Mongo DB does not like them
69- final JsonNode sanitizedJsonNode = recursiveClone (jsonNode , MongoUtils ::encodeKey );
70+ final JsonNode sanitizedJsonNode =
71+ recursiveClone (jsonNode , MongoUtils ::encodeKey , MongoUtils ::wrapInLiteral );
7072 return MAPPER .writeValueAsString (sanitizedJsonNode );
7173 }
7274
@@ -79,7 +81,10 @@ public static BasicDBObject merge(final List<BasicDBObject> basicDbObjects) {
7981 toUnmodifiableMap (Entry ::getKey , Entry ::getValue ), BasicDBObject ::new ));
8082 }
8183
82- public static JsonNode recursiveClone (JsonNode src , Function <String , String > function ) {
84+ public static JsonNode recursiveClone (
85+ JsonNode src ,
86+ UnaryOperator <String > function ,
87+ final UnaryOperator <ObjectNode > emptyObjectConverter ) {
8388 if (!src .isObject ()) {
8489 return src ;
8590 }
@@ -92,11 +97,11 @@ public static JsonNode recursiveClone(JsonNode src, Function<String, String> fun
9297 JsonNode value = next .getValue ();
9398 JsonNode newValue = value ;
9499 if (value .isObject ()) {
95- newValue = recursiveClone (value , function );
100+ newValue = recursiveClone (value , function , emptyObjectConverter );
96101 }
97102 tgt .set (newFieldName , newValue );
98103 }
99- return tgt ;
104+ return tgt . isEmpty () ? emptyObjectConverter . apply ( tgt ) : tgt ;
100105 }
101106
102107 public static Document dbObjectToDocument (BasicDBObject dbObject ) {
@@ -110,7 +115,8 @@ public static Document dbObjectToDocument(BasicDBObject dbObject) {
110115 JsonWriterSettings .builder ().outputMode (JsonMode .RELAXED ).build ();
111116 jsonString = dbObject .toJson (relaxed );
112117 JsonNode jsonNode = MAPPER .readTree (jsonString );
113- JsonNode decodedJsonNode = recursiveClone (jsonNode , MongoUtils ::decodeKey );
118+ JsonNode decodedJsonNode =
119+ recursiveClone (jsonNode , MongoUtils ::decodeKey , UnaryOperator .identity ());
114120 return new JSONDocument (decodedJsonNode );
115121 } catch (IOException e ) {
116122 // throwing exception is not very useful here.
@@ -125,4 +131,13 @@ public static ReturnDocument getReturnDocument(final ReturnDocumentType returnDo
125131 new UnsupportedOperationException (
126132 String .format ("Unhandled return document type: %s" , returnDocumentType )));
127133 }
134+
135+ private static ObjectNode wrapInLiteral (final ObjectNode objectNode ) {
136+ /* Wrapping the subDocument with $literal to be able to provide empty object "{}" as value
137+ * Throws error otherwise if empty object is provided as value.
138+ * https://jira.mongodb.org/browse/SERVER-54046 */
139+ final ObjectNode node = JsonNodeFactory .instance .objectNode ();
140+ node .set (LITERAL , objectNode );
141+ return node ;
142+ }
128143}
0 commit comments