Skip to content

Commit dc9e69b

Browse files
jstewart148jyemin
authored andcommitted
Add getList methods to the Document class
JAVA-1720
1 parent f3abdd3 commit dc9e69b

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

bson/src/main/org/bson/Document.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@
3333
import java.util.Collection;
3434
import java.util.Date;
3535
import java.util.LinkedHashMap;
36+
import java.util.List;
3637
import java.util.Map;
3738
import java.util.Set;
3839

40+
import static java.lang.String.format;
3941
import static org.bson.assertions.Assertions.notNull;
4042

4143
/**
@@ -259,6 +261,58 @@ public Date getDate(final Object key) {
259261
return (Date) get(key);
260262
}
261263

264+
/**
265+
* Gets the list value of the given key, casting the list elements to the given {@code Class<T>}. This is useful to avoid having
266+
* casts in client code, though the effect is the same.
267+
*
268+
* @param key the key
269+
* @param clazz the non-null class to cast the list value to
270+
* @param <T> the type of the class
271+
* @return the list value of the given key, or null if the instance does not contain this key.
272+
* @throws ClassCastException if the elements in the list value of the given key is not of type T or the value is not a list
273+
* @since 3.10
274+
*/
275+
public <T> List<T> getList(final Object key, final Class<T> clazz) {
276+
notNull("clazz", clazz);
277+
return constructValuesList(key, clazz, null);
278+
}
279+
280+
/**
281+
* Gets the list value of the given key, casting the list elements to {@code Class<T>} or returning the default list value if null.
282+
* This is useful to avoid having casts in client code, though the effect is the same.
283+
*
284+
* @param key the key
285+
* @param clazz the non-null class to cast the list value to
286+
* @param defaultValue what to return if the value is null
287+
* @param <T> the type of the class
288+
* @return the list value of the given key, or the default list value if the instance does not contain this key.
289+
* @throws ClassCastException if the value of the given key is not of type T
290+
* @since 3.10
291+
*/
292+
public <T> List<T> getList(final Object key, final Class<T> clazz, final List<T> defaultValue) {
293+
notNull("defaultValue", defaultValue);
294+
notNull("clazz", clazz);
295+
return constructValuesList(key, clazz, defaultValue);
296+
}
297+
298+
299+
// Construct the list of values for the specified key, or return the default value if the value is null.
300+
// A ClassCastException will be thrown if an element in the list is not of type T.
301+
@SuppressWarnings("unchecked")
302+
private <T> List<T> constructValuesList(final Object key, final Class<T> clazz, final List<T> defaultValue) {
303+
List<?> value = get(key, List.class);
304+
if (value == null) {
305+
return defaultValue;
306+
}
307+
308+
for (Object item : value) {
309+
if (!clazz.isAssignableFrom(item.getClass())) {
310+
throw new ClassCastException(format("List element cannot be cast to %s", clazz.getName()));
311+
}
312+
}
313+
return (List<T>) value;
314+
}
315+
262316
/**
263317
* Gets a JSON representation of this document using the {@link org.bson.json.JsonMode#STRICT} output mode, and otherwise the default
264318
* settings of {@link JsonWriterSettings.Builder} and {@link DocumentCodec}.

bson/src/test/unit/org/bson/types/DocumentSpecification.groovy

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,67 @@ class DocumentSpecification extends Specification {
6565
doc.get('noVal', objectId) == objectId
6666
}
6767

68+
def 'should return a list with elements of the specified class'() {
69+
when:
70+
Document doc = Document.parse("{x: 1, y: ['two', 'three'], z: [{a: 'one'}, {b:2}], w: {a: ['One', 'Two']}}")
71+
.append('numberList', Arrays.asList(10, 20.5d, 30L))
72+
List<String> defaultList = Arrays.asList('a', 'b', 'c')
73+
74+
then:
75+
doc.getList('y', String).get(0) == 'two'
76+
doc.getList('y', String).get(1) == 'three'
77+
doc.getList('z', Document).get(0).getString('a') == 'one'
78+
doc.getList('z', Document).get(1).getInteger('b') == 2
79+
doc.get('w', Document).getList('a', String).get(0) == 'One'
80+
doc.get('w', Document).getList('a', String).get(1) == 'Two'
81+
doc.getList('invalidKey', Document, defaultList).get(0) == 'a'
82+
doc.getList('invalidKey', Document, defaultList).get(1) == 'b'
83+
doc.getList('invalidKey', Document, defaultList).get(2) == 'c'
84+
doc.getList('numberList', Number).get(0) == 10
85+
doc.getList('numberList', Number).get(1) == 20.5d
86+
doc.getList('numberList', Number).get(2) == 30L
87+
}
88+
89+
def 'should return null list when key is not found'() {
90+
when:
91+
Document doc = Document.parse('{x: 1}')
92+
93+
then:
94+
doc.getList('a', String) == null
95+
}
96+
97+
def 'should return specified default value when key is not found'() {
98+
when:
99+
Document doc = Document.parse('{x: 1}')
100+
List<String> defaultList = Arrays.asList('a', 'b', 'c')
101+
102+
then:
103+
doc.getList('a', String, defaultList) == defaultList
104+
}
105+
106+
def 'should throw an exception when the list elements are not objects of the specified class'() {
107+
given:
108+
Document doc = Document.parse('{x: 1, y: [{a: 1}, {b: 2}], z: [1, 2]}')
109+
110+
when:
111+
doc.getList('x', String)
112+
113+
then:
114+
thrown(ClassCastException)
115+
116+
when:
117+
doc.getList('y', String)
118+
119+
then:
120+
thrown(ClassCastException)
121+
122+
when:
123+
doc.getList('z', String)
124+
125+
then:
126+
thrown(ClassCastException)
127+
}
128+
68129
def 'should parse a valid JSON string to a Document'() {
69130
when:
70131
Document document = Document.parse("{ 'int' : 1, 'string' : 'abc' }");

0 commit comments

Comments
 (0)