Skip to content

Commit a6f9b6b

Browse files
committed
* Root object type's like Person[].class, String[].class, can now be specified as the rootType and the return value will be Person[], String[], or a ClassCastException if the JSON data does not match the type.
* `JsonIo.formatJson()` three parameter version removed. Use the one (1) parameter API that takes the JSON to format. It runs much faster, as it no longer deserializes/serializes, but walks the JSON `String` directly.
1 parent 82208fd commit a6f9b6b

File tree

14 files changed

+624
-282
lines changed

14 files changed

+624
-282
lines changed

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ ___
2424
To include in your project:
2525
##### Gradle
2626
```groovy
27-
implementation 'com.cedarsoftware:json-io:4.29.0'
27+
implementation 'com.cedarsoftware:json-io:4.30.0'
2828
```
2929

3030
##### Maven
3131
```xml
3232
<dependency>
3333
<groupId>com.cedarsoftware</groupId>
3434
<artifactId>json-io</artifactId>
35-
<version>4.29.0</version>
35+
<version>4.30.0</version>
3636
</dependency>
3737
```
3838
___
@@ -50,23 +50,23 @@ ___
5050
>- [ ] **Java Package**: com.cedarsoftware.io
5151
>- [ ] **Java**: JDK17+ (Class file 52 format, includes module-info.class - multi-release JAR)
5252
>- [ ] **API**
53-
> - Static methods on [JsonIo](https://www.javadoc.io/doc/com.cedarsoftware/json-io/latest/com/cedarsoftware/io/JsonIo.html): [toJson()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonIo.html#toJson(java.lang.Object,com.cedarsoftware.io.WriteOptions)), [toObjects()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonIo.html#toObjects(java.lang.String,com.cedarsoftware.io.ReadOptions,java.lang.Class)), [formatJson()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonIo.html#formatJson(java.lang.String,com.cedarsoftware.io.ReadOptions,com.cedarsoftware.io.WriteOptions)), [deepCopy()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonIo.html#deepCopy(java.lang.Object,com.cedarsoftware.io.ReadOptions,com.cedarsoftware.io.WriteOptions))
53+
> - Static methods on [JsonIo](https://www.javadoc.io/doc/com.cedarsoftware/json-io/latest/com/cedarsoftware/io/JsonIo.html): [toJson()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonIo.html#toJson(java.lang.Object,com.cedarsoftware.io.WriteOptions)), [toObjects()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonIo.html#toObjects(java.lang.String,com.cedarsoftware.io.ReadOptions,java.lang.Class)), [formatJson()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonIo.html#formatJson(java.lang.String,com.cedarsoftware.io.ReadOptions,com.cedarsoftware.io.WriteOptions)), [deepCopy()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonIo.html#deepCopy(java.lang.Object,com.cedarsoftware.io.ReadOptions,com.cedarsoftware.io.WriteOptions))
5454
> - Use [ReadOptionsBuilder](/user-guide-readOptions.md) and [WriteOptionsBuilder](/user-guide-writeOptions.md) to configure `JsonIo`
55-
> - Use [JsonReader.ClassFactory](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonReader.ClassFactory.html) for difficult classes (hard to instantiate & fill)
56-
> - Use [JsonWriter.JsonClassWriter](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonWriter.JsonClassWriter.html) to customize the output JSON for a particular class
55+
> - Use [JsonReader.ClassFactory](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonReader.ClassFactory.html) for difficult classes (hard to instantiate & fill)
56+
> - Use [JsonWriter.JsonClassWriter](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonWriter.JsonClassWriter.html) to customize the output JSON for a particular class
5757
>- [ ] Updates will be 5.1.0, 5.2.0, ...
58-
>### 4.29.0 (current)
59-
>- [ ] **Version**: [4.29.0](https://www.javadoc.io/doc/com.cedarsoftware/json-io/4.29.0/index.html)
58+
>### 4.30.0 (current)
59+
>- [ ] **Version**: [4.30.0](https://www.javadoc.io/doc/com.cedarsoftware/json-io/4.30.0/index.html)
6060
>- [ ] **Bundling**: Both JPMS (Java Platform Module System) and OSGi (Open Service Gateway initiative)
6161
>- [ ] **Maintained**: Fully
6262
>- [ ] **Java Package**: com.cedarsoftware.io
6363
>- [ ] **Java**: JDK1.8+ (Class file 52 format, includes module-info.class - multi-release JAR)
6464
>- [ ] **API**
65-
> - Static methods on [JsonIo](https://www.javadoc.io/doc/com.cedarsoftware/json-io/latest/com/cedarsoftware/io/JsonIo.html): [toJson()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonIo.html#toJson(java.lang.Object,com.cedarsoftware.io.WriteOptions)), [toObjects()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonIo.html#toObjects(java.lang.String,com.cedarsoftware.io.ReadOptions,java.lang.Class)), [formatJson()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonIo.html#formatJson(java.lang.String,com.cedarsoftware.io.ReadOptions,com.cedarsoftware.io.WriteOptions)), [deepCopy()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonIo.html#deepCopy(java.lang.Object,com.cedarsoftware.io.ReadOptions,com.cedarsoftware.io.WriteOptions))
65+
> - Static methods on [JsonIo](https://www.javadoc.io/doc/com.cedarsoftware/json-io/latest/com/cedarsoftware/io/JsonIo.html): [toJson()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonIo.html#toJson(java.lang.Object,com.cedarsoftware.io.WriteOptions)), [toObjects()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonIo.html#toObjects(java.lang.String,com.cedarsoftware.io.ReadOptions,java.lang.Class)), [formatJson()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonIo.html#formatJson(java.lang.String,com.cedarsoftware.io.ReadOptions,com.cedarsoftware.io.WriteOptions)), [deepCopy()](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonIo.html#deepCopy(java.lang.Object,com.cedarsoftware.io.ReadOptions,com.cedarsoftware.io.WriteOptions))
6666
> - Use [ReadOptionsBuilder](/user-guide-readOptions.md) and [WriteOptionsBuilder](/user-guide-writeOptions.md) to configure `JsonIo`
67-
> - Use [JsonReader.ClassFactory](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonReader.ClassFactory.html) for difficult classes (hard to instantiate & fill)
68-
> - Use [JsonWriter.JsonClassWriter](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.29.0/com/cedarsoftware/io/JsonWriter.JsonClassWriter.html) to customize the output JSON for a particular class
69-
>- [ ] Updates will be 4.30.0, 4.31.0, ...
67+
> - Use [JsonReader.ClassFactory](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonReader.ClassFactory.html) for difficult classes (hard to instantiate & fill)
68+
> - Use [JsonWriter.JsonClassWriter](https://www.javadoc.io/static/com.cedarsoftware/json-io/4.30.0/com/cedarsoftware/io/JsonWriter.JsonClassWriter.html) to customize the output JSON for a particular class
69+
>- [ ] Updates will be 4.31.0, 4.32.0, ...
7070
>### 4.14.x (supported)
7171
>- [ ] **Version**: [4.14.3](https://www.javadoc.io/doc/com.cedarsoftware/json-io/4.14.3/index.html)
7272
>- [ ] **Bundling**: Both JPMS (Java Platform Module System) and OSGi (Open Service Gateway initiative)

changelog.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
### Revision History
2+
#### 4.30.0
3+
* Root object type's like `Person[].class,` `String[].class,` can now be specified as the `rootType` and the return value will be `Person[],` `String[],` or a `ClassCastException` if the JSON data does not match the type.
4+
* `JsonIo.formatJson()` three parameter version removed. Use the one (1) parameter API that takes the JSON to format. It runs much faster, as it no longer deserializes/serializes, but walks the JSON `String` directly.
25
#### 4.29.0
3-
* Consumed `java-util` `ClassUtilities.getClassLoader(),` which obtains the classLoader in a more robust way and works in OSGi and JPMS environment or non-framework environment
6+
* Consumed `java-util's` `ClassUtilities.getClassLoader(),` which obtains the classLoader in a more robust way and works in OSGi and JPMS environment or non-framework environment
47
* Removed `slf4j` and `logback-classic` from `test` dependencies
58
* Merged in PR #297 by DaniellaHubble: Fix test that fails unexpectedly in `testEnumWithPrivateMembersAsField_withPrivatesOn()`
69
* Updated [java-util](https://github.com/jdereg/java-util/blob/master/changelog.md) from `2.15.0` to `2.17.0.`

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<groupId>com.cedarsoftware</groupId>
66
<artifactId>json-io</artifactId>
77
<packaging>bundle</packaging>
8-
<version>4.29.0</version>
8+
<version>4.30.0</version>
99
<description>Java JSON serialization</description>
1010
<url>https://github.com/jdereg/json-io</url>
1111

src/main/java/com/cedarsoftware/io/JsonIo.java

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.Map;
1212
import java.util.Set;
1313

14+
import com.cedarsoftware.io.prettyprint.JsonPrettyPrinter;
1415
import com.cedarsoftware.util.ClassUtilities;
1516
import com.cedarsoftware.util.Convention;
1617
import com.cedarsoftware.util.FastByteArrayInputStream;
@@ -35,7 +36,7 @@
3536
* streaming mode. The JSON specification has great primitives which are universally useful in many languages. In
3637
* Java that is boolean, null, long [or BigInteger], and double [or BigDecimal], and String.<br/>
3738
* <br/>
38-
* When JsonObject is returned [option #3 or #4 above with readOptions.returnType(ReturnType.JSON_VALUES)], your root
39+
* When JsonObject is returned [option #3 or #4 above with readOptions.returnAsNativeJsonObjects(), your root
3940
* value will represent one of:
4041
* <ul>JSON object {...}<br/>
4142
* JSON array [...]<br/>
@@ -224,33 +225,11 @@ public static <T> T toObjects(JsonObject jsonObject, ReadOptions readOptions, Cl
224225

225226
/**
226227
* Format the passed in JSON into multi-line, indented format, commonly used in JSON online editors.
227-
* @param readOptions ReadOptions to control the feature options. Can be null to take the defaults.
228-
* @param writeOptions WriteOptions to control the feature options. Can be null to take the defaults.
229228
* @param json String JSON content.
230229
* @return String JSON formatted in human-readable, standard multi-line, indented format.
231230
*/
232-
public static String formatJson(String json, ReadOptions readOptions, WriteOptions writeOptions) {
233-
if (writeOptions == null || !writeOptions.isPrettyPrint()) {
234-
writeOptions = new WriteOptionsBuilder(writeOptions).prettyPrint(true).build();
235-
}
236-
237-
if (readOptions == null) {
238-
readOptions = ReadOptionsBuilder.getDefaultReadOptions();
239-
} else if (!readOptions.isReturningJavaObjects()) {
240-
readOptions = new ReadOptionsBuilder(readOptions).returnAsJavaObjects().build();
241-
}
242-
243-
Object object = toObjects(json, readOptions, null);
244-
return toJson(object, writeOptions);
245-
}
246-
247-
/**
248-
* Format the passed in JSON into multi-line, indented format, commonly used in JSON online editors.
249-
* @param json String JSON content.
250-
* @return String JSON formatted in human readable, standard multi-line, indented format.
251-
*/
252231
public static String formatJson(String json) {
253-
return formatJson(json, null, null);
232+
return JsonPrettyPrinter.prettyPrint(json);
254233
}
255234

256235
/**
@@ -270,8 +249,6 @@ public static <T> T deepCopy(Object source, ReadOptions readOptions, WriteOption
270249
writeOptions = new WriteOptionsBuilder(writeOptions).showTypeInfoMinimal().shortMetaKeys(true).build();
271250
if (readOptions == null) {
272251
readOptions = ReadOptionsBuilder.getDefaultReadOptions();
273-
} else {
274-
readOptions = new ReadOptionsBuilder(readOptions).build();
275252
}
276253

277254
String json = toJson(source, writeOptions);
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package com.cedarsoftware.io.prettyprint;
2+
3+
/**
4+
* @author John DeRegnaucourt ([email protected])
5+
* <br>
6+
* Copyright (c) Cedar Software LLC
7+
* <br><br>
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
* <br><br>
12+
* <a href="http://www.apache.org/licenses/LICENSE-2.0">License</a>
13+
* <br><br>
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
public class JsonPrettyPrinter {
21+
/**
22+
* Takes a compact JSON string and returns a pretty-printed version with proper indentation.
23+
*
24+
* @param json The compact JSON string.
25+
* @return A pretty-printed JSON string.
26+
*/
27+
public static String prettyPrint(String json) {
28+
if (json == null || json.isEmpty()) {
29+
return json;
30+
}
31+
32+
json = json.trim();
33+
34+
// Check if the root is a primitive (number, boolean, string, or null)
35+
if (isPrimitive(json)) {
36+
// For primitives, return the trimmed string
37+
return json;
38+
}
39+
40+
StringBuilder prettyJson = new StringBuilder();
41+
int indentLevel = 0;
42+
boolean inString = false;
43+
boolean escape = false;
44+
45+
for (int i = 0; i < json.length(); i++) {
46+
char currentChar = json.charAt(i);
47+
48+
if (escape) {
49+
prettyJson.append(currentChar);
50+
escape = false;
51+
continue;
52+
}
53+
54+
if (currentChar == '\\') {
55+
escape = true;
56+
prettyJson.append(currentChar);
57+
continue;
58+
}
59+
60+
if (currentChar == '\"') {
61+
inString = !inString;
62+
prettyJson.append(currentChar);
63+
continue;
64+
}
65+
66+
if (!inString) {
67+
switch (currentChar) {
68+
case '{':
69+
case '[':
70+
prettyJson.append(currentChar);
71+
// Peek ahead to see if the next non-whitespace character is '}' or ']'
72+
if (!isNextCharClosing(json, i)) {
73+
prettyJson.append('\n');
74+
indentLevel++;
75+
appendIndentation(prettyJson, indentLevel);
76+
}
77+
break;
78+
case '}':
79+
case ']':
80+
// Newline before the closing brace/bracket if the previous non-whitespace character is not '{' or '['
81+
if (!isPrevCharOpening(json, i)) {
82+
prettyJson.append('\n');
83+
indentLevel--;
84+
appendIndentation(prettyJson, indentLevel);
85+
} else {
86+
// Decrease indentation level even if no newline is added
87+
indentLevel--;
88+
}
89+
prettyJson.append(currentChar);
90+
break;
91+
case ',':
92+
prettyJson.append(currentChar);
93+
// Peek ahead to see if the next non-whitespace character is not '}' or ']'
94+
if (!isNextCharClosing(json, i)) {
95+
prettyJson.append('\n');
96+
appendIndentation(prettyJson, indentLevel);
97+
}
98+
break;
99+
case ':':
100+
prettyJson.append(currentChar);
101+
// prettyJson.append(' '); // uncomment for space after the colon in "field": "value"
102+
break;
103+
default:
104+
if (!Character.isWhitespace(currentChar)) {
105+
prettyJson.append(currentChar);
106+
}
107+
break;
108+
}
109+
} else {
110+
prettyJson.append(currentChar);
111+
}
112+
}
113+
114+
return prettyJson.toString();
115+
}
116+
117+
/**
118+
* Checks if the given JSON string represents a primitive value.
119+
*
120+
* @param json The JSON string.
121+
* @return True if the JSON is a primitive, false otherwise.
122+
*/
123+
private static boolean isPrimitive(String json) {
124+
// Check for JSON null, boolean, number, or string
125+
return isJsonNull(json) || isJsonBoolean(json) || isJsonNumber(json) || isJsonString(json);
126+
}
127+
128+
private static boolean isJsonNull(String json) {
129+
return "null".equals(json);
130+
}
131+
132+
private static boolean isJsonBoolean(String json) {
133+
return "true".equals(json) || "false".equals(json);
134+
}
135+
136+
private static boolean isJsonNumber(String json) {
137+
try {
138+
Double.parseDouble(json);
139+
return true;
140+
} catch (NumberFormatException e) {
141+
return false;
142+
}
143+
}
144+
145+
private static boolean isJsonString(String json) {
146+
return json.startsWith("\"") && json.endsWith("\"");
147+
}
148+
149+
/**
150+
* Checks if the next non-whitespace character is a closing brace '}' or bracket ']'.
151+
*
152+
* @param json The JSON string.
153+
* @param index The current index.
154+
* @return True if the next non-whitespace character is '}' or ']', false otherwise.
155+
*/
156+
private static boolean isNextCharClosing(String json, int index) {
157+
for (int i = index + 1; i < json.length(); i++) {
158+
char c = json.charAt(i);
159+
if (Character.isWhitespace(c)) {
160+
continue;
161+
}
162+
return c == '}' || c == ']';
163+
}
164+
return false;
165+
}
166+
167+
/**
168+
* Checks if the previous non-whitespace character is an opening brace '{' or bracket '['.
169+
*
170+
* @param json The JSON string.
171+
* @param index The current index.
172+
* @return True if the previous non-whitespace character is '{' or '[', false otherwise.
173+
*/
174+
private static boolean isPrevCharOpening(String json, int index) {
175+
for (int i = index - 1; i >= 0; i--) {
176+
char c = json.charAt(i);
177+
if (Character.isWhitespace(c)) {
178+
continue;
179+
}
180+
return c == '{' || c == '[';
181+
}
182+
return false;
183+
}
184+
185+
/**
186+
* Appends indentation spaces to the StringBuilder based on the current indentation level.
187+
*
188+
* @param sb The StringBuilder to append to.
189+
* @param indentLevel The current indentation level.
190+
*/
191+
private static void appendIndentation(StringBuilder sb, int indentLevel) {
192+
final int INDENT_SIZE = 2; // Number of spaces for each indentation level
193+
for (int i = 0; i < indentLevel * INDENT_SIZE; i++) {
194+
sb.append(' ');
195+
}
196+
}
197+
}

src/test/java/com/cedarsoftware/io/TimeZoneTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ void testTimeZone_inCollection()
142142
String json = TestUtil.toJson(expected);
143143
TestUtil.printLine("json=" + json);
144144

145-
List<TimeZone> actual = (List<TimeZone>) TestUtil.toObjects(json, null);
145+
List<TimeZone> actual = TestUtil.toObjects(json, null);
146146

147147
// assert
148148
assertThat(actual).hasSize(2)
@@ -158,7 +158,7 @@ void testTimeZone_inMap_asValue()
158158
String json = TestUtil.toJson(expected);
159159
TestUtil.printLine("json=" + json);
160160

161-
Map actual = (Map<String, TimeZone>) TestUtil.toObjects(json, null);
161+
Map actual = TestUtil.toObjects(json, null);
162162

163163
assertThat(actual).hasSize(1)
164164
.containsAllEntriesOf(expected);

0 commit comments

Comments
 (0)