View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.builder;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertThrows;
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.Date;
26  import java.util.HashMap;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.commons.lang3.builder.ToStringStyleTest.Person;
32  import org.junit.jupiter.api.AfterEach;
33  import org.junit.jupiter.api.BeforeEach;
34  import org.junit.jupiter.api.Test;
35  
36  /**
37   * Tests {@link JsonToStringStyleTest}.
38   */
39  class JsonToStringStyleTest extends AbstractBuilderTest {
40  
41      static class AcademyClass {
42          Teacher teacher;
43          List<Student> students;
44  
45          public List<Student> getStudents() {
46              return students;
47          }
48  
49          public Teacher getTeacher() {
50              return teacher;
51          }
52  
53          public void setStudents(final List<Student> students) {
54              this.students = students;
55          }
56  
57          public void setTeacher(final Teacher teacher) {
58              this.teacher = teacher;
59          }
60  
61          @Override
62          public String toString() {
63              return ToStringBuilder.reflectionToString(this);
64          }
65      }
66  
67      enum EmptyEnum {
68      }
69  
70      enum Hobby {
71          SPORT,
72          BOOK,
73          MUSIC
74      }
75  
76      /**
77       * An object with a Map field used to test {@code ToStringStyle.JsonToStringStyle}.
78       */
79      static class InnerMapObject {
80  
81          /**
82           * Test String field.
83           */
84          String pid;
85  
86          /**
87           * Test inner map field.
88           */
89          Map<String, Object> map;
90      }
91  
92      /**
93       * An object with nested object structures used to test {@code ToStringStyle.JsonToStringStyle}.
94       */
95      static class NestingPerson {
96  
97          /**
98           * Test String field.
99           */
100         String pid;
101 
102         /**
103          * Test nested object field.
104          */
105         Person person;
106     }
107 
108     static class Student {
109         List<Hobby> hobbies;
110 
111         public List<Hobby> getHobbies() {
112             return hobbies;
113         }
114 
115         public void setHobbies(final List<Hobby> hobbies) {
116             this.hobbies = hobbies;
117         }
118 
119         @Override
120         public String toString() {
121             return ToStringBuilder.reflectionToString(this);
122         }
123     }
124 
125     static class Teacher {
126         Hobby[] hobbies;
127 
128         public Hobby[] getHobbies() {
129             return hobbies;
130         }
131 
132         public void setHobbies(final Hobby[] hobbies) {
133             this.hobbies = hobbies;
134         }
135 
136         @Override
137         public String toString() {
138             return ToStringBuilder.reflectionToString(this);
139         }
140     }
141 
142     private final Integer base = Integer.valueOf(5);
143 
144     @BeforeEach
145     public void setUp() {
146         ToStringBuilder.setDefaultStyle(ToStringStyle.JSON_STYLE);
147     }
148 
149     @AfterEach
150     public void tearDown() {
151         ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE);
152     }
153 
154     @Test
155     void testAppendSuper() {
156         assertEquals(
157                 "{}",
158                 new ToStringBuilder(base).appendSuper(
159                         "Integer@8888[" + System.lineSeparator() + "]")
160                         .toString());
161         assertEquals(
162                 "{}",
163                 new ToStringBuilder(base).appendSuper(
164                         "Integer@8888[" + System.lineSeparator() + "  null"
165                                 + System.lineSeparator() + "]").toString());
166         assertEquals(
167                 "{\"a\":\"hello\"}",
168                 new ToStringBuilder(base)
169                         .appendSuper(
170                                 "Integer@8888[" + System.lineSeparator()
171                                         + "]").append("a", "hello").toString());
172         assertEquals(
173                 "{\"a\":\"hello\"}",
174                 new ToStringBuilder(base)
175                         .appendSuper(
176                                 "Integer@8888[" + System.lineSeparator()
177                                         + "  null" + System.lineSeparator()
178                                         + "]").append("a", "hello").toString());
179         assertEquals("{\"a\":\"hello\"}", new ToStringBuilder(base)
180                 .appendSuper(null).append("a", "hello").toString());
181 
182         assertEquals("{\"a\":\"hello\",\"b\":\"world\"}", new ToStringBuilder(base)
183                 .appendSuper("{\"a\":\"hello\"}").append("b", "world").toString());
184     }
185 
186     @Test
187     void testArray() {
188         final Person p = new Person();
189         p.name = "Jane Doe";
190         p.age = 25;
191         p.smoker = true;
192 
193         assertEquals(
194                 "{\"name\":\"Jane Doe\",\"age\":25,\"smoker\":true,\"groups\":['admin', 'manager', 'user']}",
195                 new ToStringBuilder(p).append("name", p.name)
196                         .append("age", p.age).append("smoker", p.smoker)
197                         .append("groups", new Object() {
198                             @Override
199                             public String toString() {
200                                 return "['admin', 'manager', 'user']";
201                             }
202                         })
203                         .toString());
204     }
205 
206     @Test
207     void testArrayEnum() {
208         final Teacher teacher = new Teacher();
209         final Hobby[] hobbies = new Hobby[3];
210         hobbies[0] = Hobby.BOOK;
211         hobbies[1] = Hobby.SPORT;
212         hobbies[2] = Hobby.MUSIC;
213 
214         teacher.setHobbies(hobbies);
215 
216         assertEqualsIfAccessible(teacher.toString(), "{\"hobbies\":[\"BOOK\",\"SPORT\",\"MUSIC\"]}");
217         teacher.setHobbies(new Hobby[0]);
218         assertEqualsIfAccessible(teacher.toString(), "{\"hobbies\":[]}");
219         teacher.setHobbies(null);
220         assertEquals(teacher.toString(), "{\"hobbies\":null}");
221     }
222 
223     @Test
224     void testBlank() {
225         assertEquals("{}", new ToStringBuilder(base).toString());
226     }
227 
228     @Test
229     void testBooleanArray() {
230         final boolean[] array = { true, false };
231         final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
232         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
233         assertEquals("{\"booleanArray\":[true,false]}", toStringBuilder.append("booleanArray", array).toString());
234         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
235         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
236         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
237     }
238 
239     @Test
240     void testByteArray() {
241         final byte[] array = { 1, 2, -3, 4 };
242         final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
243         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
244         assertEquals("{\"byteArray\":[1,2,-3,4]}", toStringBuilder.append("byteArray", array).toString());
245         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
246         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
247         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
248     }
249 
250     @Test
251     void testChar() {
252         assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append('A').toString());
253         assertEquals("{\"a\":\"A\"}", new ToStringBuilder(base).append("a", 'A').toString());
254         assertEquals("{\"a\":\"A\",\"b\":\"B\"}", new ToStringBuilder(base).append("a", 'A').append("b", 'B').toString());
255     }
256 
257     @Test
258     void testCharArray() {
259         final char[] array = { '1', '2', '3', '4' };
260         final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
261         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
262         assertEquals("{\"charArray\":[\"1\",\"2\",\"3\",\"4\"]}", toStringBuilder.append("charArray", array).toString());
263         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
264         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
265         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
266     }
267 
268     @Test
269     void testCombineListAndEnum() {
270         final Teacher teacher = new Teacher();
271 
272         final Hobby[] teacherHobbies = new Hobby[3];
273         teacherHobbies[0] = Hobby.BOOK;
274         teacherHobbies[1] = Hobby.SPORT;
275         teacherHobbies[2] = Hobby.MUSIC;
276 
277         teacher.setHobbies(teacherHobbies);
278 
279         final Student john = new Student();
280         john.setHobbies(Arrays.asList(Hobby.BOOK, Hobby.MUSIC));
281 
282         final Student alice = new Student();
283         alice.setHobbies(new ArrayList<>());
284 
285         final Student bob = new Student();
286         bob.setHobbies(Collections.singletonList(Hobby.BOOK));
287 
288         final ArrayList<Student> students = new ArrayList<>();
289         students.add(john);
290         students.add(alice);
291         students.add(bob);
292 
293         final AcademyClass academyClass = new AcademyClass();
294         academyClass.setStudents(students);
295         academyClass.setTeacher(teacher);
296 
297         assertEqualsIfAccessible(academyClass.toString(),
298                 "{\"students\":[{\"hobbies\":[\"BOOK\",\"MUSIC\"]},{\"hobbies\":[]},{\"hobbies\":[\"BOOK\"]}],\"teacher\":{\"hobbies\":[\"BOOK\",\"SPORT\",\"MUSIC\"]}}");
299     }
300 
301     @Test
302     void testDate() {
303         final Date now = new Date();
304         final Date afterNow = new Date(System.currentTimeMillis() + 1);
305 
306         assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append(now).toString());
307 
308         assertEquals("{\"now\":\"" + now + "\"}", new ToStringBuilder(base).append("now", now).toString());
309         assertEquals("{\"now\":\"" + now + "\",\"after\":\"" + afterNow + "\"}",
310                 new ToStringBuilder(base).append("now", now).append("after", afterNow).toString());
311     }
312 
313     @Test
314     void testDoubleArray() {
315         final double[] array = { 1, 2, -3, 4 };
316         final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
317         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
318         assertEquals("{\"doubleArray\":[1.0,2.0,-3.0,4.0]}", toStringBuilder.append("doubleArray", array).toString());
319         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
320         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
321         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
322     }
323 
324     @Test
325     void testFloatArray() {
326         final float[] array = { 1, 2, -3, 4 };
327         final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
328         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
329         assertEquals("{\"floatArray\":[1.0,2.0,-3.0,4.0]}", toStringBuilder.append("floatArray", array).toString());
330         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
331         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
332         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
333     }
334 
335     @Test
336     void testIntArray() {
337         final int[] array = { 1, 2, -3, 4 };
338         final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
339         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
340         assertEquals("{\"intArray\":[1,2,-3,4]}", toStringBuilder.append("intArray", array).toString());
341         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
342         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
343         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
344     }
345 
346     @Test
347     void testLANG1395() {
348         assertEquals("{\"name\":\"value\"}", new ToStringBuilder(base).append("name", "value").toString());
349         assertEquals("{\"name\":\"\"}", new ToStringBuilder(base).append("name", "").toString());
350         assertEquals("{\"name\":\"\\\"\"}", new ToStringBuilder(base).append("name", '"').toString());
351         assertEquals("{\"name\":\"\\\\\"}", new ToStringBuilder(base).append("name", '\\').toString());
352         assertEquals("{\"name\":\"Let's \\\"quote\\\" this\"}", new ToStringBuilder(base).append("name", "Let's \"quote\" this").toString());
353     }
354 
355     @Test
356     void testLANG1396() {
357         assertEquals("{\"Let's \\\"quote\\\" this\":\"value\"}", new ToStringBuilder(base).append("Let's \"quote\" this", "value").toString());
358     }
359 
360     @Test
361     void testList() {
362         final Student student = new Student();
363         final ArrayList<Hobby> objects = new ArrayList<>();
364 
365         objects.add(Hobby.BOOK);
366         objects.add(Hobby.SPORT);
367         objects.add(Hobby.MUSIC);
368 
369         student.setHobbies(objects);
370 
371         assertEqualsIfAccessible(student.toString(), "{\"hobbies\":[\"BOOK\",\"SPORT\",\"MUSIC\"]}");
372         student.setHobbies(new ArrayList<>());
373         assertEqualsIfAccessible(student.toString(), "{\"hobbies\":[]}");
374         student.setHobbies(null);
375         assertEquals(student.toString(), "{\"hobbies\":null}");
376     }
377 
378     @Test
379     void testLong() {
380         assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append(3L).toString());
381 
382         assertEquals("{\"a\":3}", new ToStringBuilder(base).append("a", 3L)
383                 .toString());
384         assertEquals("{\"a\":3,\"b\":4}",
385                 new ToStringBuilder(base).append("a", 3L).append("b", 4L)
386                         .toString());
387     }
388 
389     @Test
390     void testLongArray() {
391         final long[] array = { 1, 2, -3, 4 };
392         final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
393         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
394         assertEquals("{\"longArray\":[1,2,-3,4]}", toStringBuilder.append("longArray", array).toString());
395         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
396         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
397         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
398     }
399 
400     @Test
401     void testLongArrayArray() {
402         final long[][] array = { { 1, 2 }, null, { 5 } };
403         final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
404         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
405         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
406         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((long[][]) null).toString());
407         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
408     }
409 
410     @Test
411     void testMapSkipNullKey() {
412         final Map<String, Object> map = new LinkedHashMap<>();
413         map.put("k1", "v1");
414         map.put(null, "v2");
415 
416         assertEquals("{\"map\":{\"k1\":\"v1\"}}",
417                 new ToStringBuilder(base).append("map", map).toString());
418     }
419 
420     @Test
421     void testNestedMaps() {
422         final Map<String, Object> innerMap = new LinkedHashMap<>();
423         innerMap.put("k2.1", "v2.1");
424         innerMap.put("k2.2", "v2.2");
425         final Map<String, Object> baseMap = new LinkedHashMap<>();
426         baseMap.put("k1", "v1");
427         baseMap.put("k2", innerMap);
428 
429         final InnerMapObject object = new InnerMapObject() {
430             @Override
431             public String toString() {
432                 return new ToStringBuilder(this).append("pid", this.pid).append("map", this.map).toString();
433             }
434         };
435         object.pid = "dummy-text";
436         object.map = baseMap;
437 
438         assertEquals("{\"object\":{\"pid\":\"dummy-text\",\"map\":{\"k1\":\"v1\",\"k2\":{\"k2.1\":\"v2.1\",\"k2.2\":\"v2.2\"}}}}",
439                 new ToStringBuilder(base).append("object", object).toString());
440     }
441 
442     @Test
443     void testNestingPerson() {
444         final Person p = new Person() {
445             @Override
446             public String toString() {
447                 return new ToStringBuilder(this).append("name", this.name).append("age", this.age).append("smoker", this.smoker).toString();
448             }
449         };
450         p.name = "Jane Doe";
451         p.age = 25;
452         p.smoker = true;
453 
454         final NestingPerson nestP = new NestingPerson();
455         nestP.pid = "#1@Jane";
456         nestP.person = p;
457 
458         assertEquals("{\"pid\":\"#1@Jane\",\"person\":{\"name\":\"Jane Doe\",\"age\":25,\"smoker\":true}}",
459                 new ToStringBuilder(nestP).append("pid", nestP.pid).append("person", nestP.person).toString());
460     }
461 
462     @Test
463     void testNull() {
464         assertEquals("null", new ToStringBuilder(null).toString());
465     }
466 
467     @Test
468     void testObject() {
469         final Integer i3 = Integer.valueOf(3);
470         final Integer i4 = Integer.valueOf(4);
471         assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append((Object) null).toString());
472         assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append(i3).toString());
473         assertEquals("{\"a\":null}", new ToStringBuilder(base).append("a", (Object) null).toString());
474         assertEquals("{\"a\":3}", new ToStringBuilder(base).append("a", i3).toString());
475         assertEquals("{\"a\":3,\"b\":4}", new ToStringBuilder(base).append("a", i3).append("b", i4).toString());
476         assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append("a", i3, false).toString());
477         assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append("a", new ArrayList<>(), false).toString());
478         assertEquals("{\"a\":[]}", new ToStringBuilder(base).append("a", new ArrayList<>(), true).toString());
479         assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append("a", new HashMap<>(), false).toString());
480         assertEquals("{\"a\":{}}", new ToStringBuilder(base).append("a", new HashMap<>(), true).toString());
481         assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append("a", (Object) new String[0], false).toString());
482         assertEquals("{\"a\":[]}", new ToStringBuilder(base).append("a", (Object) new String[0], true).toString());
483         assertThrows(UnsupportedOperationException.class, () -> new ToStringBuilder(base).append("a", (Object) new int[] { 1, 2, 3 }, false).toString());
484         assertEquals("{\"a\":[1,2,3]}", new ToStringBuilder(base).append("a", (Object) new int[] { 1, 2, 3 }, true).toString());
485         assertThrows(UnsupportedOperationException.class,
486                 () -> new ToStringBuilder(base).append("a", (Object) new String[] { "v", "x", "y", "z" }, false).toString());
487         assertEquals("{\"a\":[\"v\",\"x\",\"y\",\"z\"]}", new ToStringBuilder(base).append("a", (Object) new String[] { "v", "x", "y", "z" }, true).toString());
488     }
489 
490     @Test
491     void testObjectArray() {
492         final Object[] array = { null, base, new int[] { 3, 6 } };
493         final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
494         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
495         assertEquals("{\"objectArray\":[null,5,[3,6]]}", toStringBuilder.append("objectArray", array).toString());
496         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
497         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object[]) null).toString());
498         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
499     }
500 
501     @Test
502     void testObjectWithInnerMap() {
503         final Map<String, Object> map = new LinkedHashMap<>();
504         map.put("k1", "value1");
505         map.put("k2", 2);
506 
507         final InnerMapObject object = new InnerMapObject() {
508             @Override
509             public String toString() {
510                 return new ToStringBuilder(this).append("pid", this.pid).append("map", this.map).toString();
511             }
512         };
513         object.pid = "dummy-text";
514         object.map = map;
515 
516         assertEquals("{\"object\":{\"pid\":\"dummy-text\",\"map\":{\"k1\":\"value1\",\"k2\":2}}}",
517                 new ToStringBuilder(base).append("object", object).toString());
518     }
519 
520     @Test
521     void testPerson() {
522         final Person p = new Person();
523         p.name = "Jane Doe";
524         p.age = 25;
525         p.smoker = true;
526 
527         assertEquals(
528                 "{\"name\":\"Jane Doe\",\"age\":25,\"smoker\":true}",
529                 new ToStringBuilder(p).append("name", p.name)
530                         .append("age", p.age).append("smoker", p.smoker)
531                         .toString());
532     }
533 
534     @Test
535     void testRootMap() {
536         final Map<String, Object> map = new LinkedHashMap<>();
537         map.put("k1", "v1");
538         map.put("k2", 2);
539 
540         assertEquals("{\"map\":{\"k1\":\"v1\",\"k2\":2}}",
541                 new ToStringBuilder(base).append("map", map).toString());
542     }
543 
544     @Test
545     void testShortArray() {
546         final short[] array = { 1, 2, -3, 4 };
547         final ToStringBuilder toStringBuilder = new ToStringBuilder(base);
548         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append(array).toString());
549         assertEquals("{\"shortArray\":[1,2,-3,4]}", toStringBuilder.append("shortArray", array).toString());
550         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
551         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((long[]) null).toString());
552         assertThrows(UnsupportedOperationException.class, () -> toStringBuilder.append((Object) array).toString());
553     }
554 }