Skip to content

Commit f5da59f

Browse files
authored
Merge pull request #1 from stevenlagoy/stronger-typing
Stronger typing
2 parents dec7fda + c9c1140 commit f5da59f

File tree

7 files changed

+227
-49
lines changed

7 files changed

+227
-49
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Add this dependency to your `pom.xml`:
1919
<dependency>
2020
<groupId>com.github.stevenlagoy</groupId>
2121
<artifactId>json-java-objectifier</artifactId>
22-
<version>1.0.4</version>
22+
<version>1.0.5</version>
2323
</dependency>
2424
```
2525

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>com.github.stevenlagoy</groupId>
66
<artifactId>json-java-objectifier</artifactId>
7-
<version>1.0.4</version>
7+
<version>1.0.5</version>
88
<packaging>jar</packaging>
99
<name>JSON Java Objectifier</name>
1010
<description>Java tools for converting JSON into typed object structures</description>

src/main/java/core/JSONObject.java

Lines changed: 38 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.util.ArrayList;
44
import java.util.Iterator;
55
import java.util.List;
6-
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
76

87
/**
98
* A JSONObject is a custom data structure that represents a JSON object. It supports nested key-value pairs, arrays (as
@@ -57,6 +56,7 @@ public class JSONObject implements Iterable<Object> {
5756
*/
5857
private static boolean isValidJsonType(Object value) {
5958
return (
59+
value == null ||
6060
value instanceof String ||
6161
value instanceof Number ||
6262
value instanceof JSONObject ||
@@ -94,10 +94,8 @@ public JSONObject(String key) {
9494
/**
9595
* Create a {@code JSONObject} with the given key and value.
9696
*
97-
* @param key
98-
* A {@code String} key
99-
* @param value
100-
* A valid JSON value
97+
* @param key Valid {@code String} key for JSON Object
98+
* @param value Valid JSON value ({@code String}, {@code Number}, {@code JSONObject}, {@code List<>}, {@code Boolean}, {@code Object extends Jsonic})
10199
*/
102100
public JSONObject(String key, Object value) {
103101
if (!isValidJsonType(value)) {
@@ -107,11 +105,12 @@ public JSONObject(String key, Object value) {
107105
this.value = value;
108106
if (value == null)
109107
type = null;
110-
else if (value instanceof List<?>)
108+
else if (value instanceof List<?>) {
111109
if (isJSONList(getAsList()))
112110
type = JSONObject.class;
113111
else
114112
type = ArrayList.class;
113+
}
115114
else
116115
type = value.getClass();
117116
}
@@ -144,46 +143,51 @@ public Object getValue() {
144143
/**
145144
* Return the value as the given class.
146145
*
147-
* @param clazz
148-
* An existant class to cast this object's value into.
146+
* @param clazz Existant class to cast this object's value into.
149147
*
150-
* @return The value as a type of the given class, or null if uncastable.
148+
* @return Value as a type of the given class.
149+
* @throws ClassCastException if the value is not null and is not assignable to the type T.
151150
*/
152-
public <T> T getValueAs(Class<T> clazz) {
153-
try {
154-
return clazz.cast(value);
155-
} catch (ClassCastException e) {
156-
e.printStackTrace();
157-
return null;
158-
}
151+
public <T> T getValueAs(Class<T> clazz) throws ClassCastException {
152+
return clazz.cast(value);
159153
}
160154

155+
/** Returns the value of this JSONObject as a String, or throws a ClassCastException if unable. */
161156
public String getAsString() {
162-
return value instanceof String ? (String) value : null;
157+
if (value instanceof String) return (String) value;
158+
else throw new ClassCastException("Cannot cast non-String value to String.");
163159
}
164160

161+
/** Returns the value of this JSONObject as a Number, or throws a ClassCastException if unable. */
165162
public Number getAsNumber() {
166-
return value instanceof Number ? (Number) value : null;
163+
if (value instanceof Number) return (Number) value;
164+
else throw new ClassCastException("Cannot cast non-Number value to Number.");
167165
}
168166

169-
@SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL")
167+
/** Returns the value of this JSONObject as a Boolean, or throws a ClassCastException if unable. */
170168
public Boolean getAsBoolean() {
171-
return value instanceof Boolean ? (Boolean) value : null;
169+
if (value instanceof Boolean) return (Boolean) value;
170+
else throw new ClassCastException("Cannot cast non-Boolean value to Boolean.");
172171
}
173172

173+
/** Returns the value of this JSONObject as another JSONObject, or {@code null} if unable. */
174174
public JSONObject getAsObject() {
175175

176-
if (value instanceof JSONObject) return (JSONObject) value;
176+
return this;
177177

178-
if (value instanceof List<?> && type.equals(JSONObject.class)) {
179-
return this;
180-
}
178+
// if (value instanceof JSONObject) return (JSONObject) value;
181179

182-
return null;
180+
// if (value instanceof List<?> && type.equals(JSONObject.class)) {
181+
// return this;
182+
// }
183+
184+
// return null;
183185
}
184186

187+
/** Returns the value of this JSONObject as a List, or throws a ClassCastException if unable. */
185188
public List<?> getAsList() {
186-
return value instanceof List<?> ? (List<?>) value : null;
189+
if (value instanceof List<?>) return (List<?>) value;
190+
else throw new ClassCastException("Cannot cast non-List value to List.");
187191
}
188192

189193
public void setValue(Object value) {
@@ -226,10 +230,9 @@ public Class<? extends Object> getType() {
226230
* Search the JSONObject tree structure for a JSONObject with the given key, and return the value of that
227231
* JSONObject.
228232
*
229-
* @param key
230-
* A String key to search for within the tree structure.
233+
* @param key String key to search for within the tree structure.
231234
*
232-
* @return The value of the JSONObject with the given key, or null if unfound.
235+
* @return Value of the JSONObject with the given key, or null if unfound.
233236
*/
234237
public Object get(String key) {
235238
if (this.key.equals(key))
@@ -253,7 +256,7 @@ public Object get(String key) {
253256
/**
254257
* Get the native type of the inner value of this JSONObject.
255258
*
256-
* @return The class of the value (String, Number, JSONObject, List<?>, Boolean, or null)
259+
* @return Class of the value (String, Number, JSONObject, List<?>, Boolean, or null)
257260
*/
258261
public Class<?> getInnerType() {
259262
return value.getClass();
@@ -265,8 +268,7 @@ public Class<?> getInnerType() {
265268
* Traverse the tree structure of this JSONObject inorder and return an indexable list of the objects' values. Start
266269
* with the leftmost value, then the root value, then the rightmost value, recursively.
267270
*
268-
* @param result
269-
* A List<Object> to be populated with the values from the inorder traversal.
271+
* @param result List<Object> to be populated with the values from the inorder traversal.
270272
*/
271273
public void inorderTraversal(List<Object> result) {
272274
if (value == null) {
@@ -297,12 +299,10 @@ public Iterator<Object> iterator() {
297299
// Stringify methods
298300

299301
/**
300-
* Turn this JSONObject into a String representation. Should result in a functionally identical String to the JSON
301-
* object which was read to create this JSONObject.
302-
*
303-
* @return The String representation of this JSONObject
302+
* Turn this JSONObject into a String representation. Will result in a functionally identical String to the theoretical JSON
303+
* file which was parsed to create this JSONObject.
304304
*
305-
* @see JSONObject#toString(int)
305+
* @return String representation of this JSONObject
306306
*/
307307
@Override
308308
public String toString() {
@@ -317,8 +317,7 @@ public int hashCode() {
317317
/**
318318
* Determine whether this JSONObect is equal to the other JSONObject by comparing their String representations.
319319
*
320-
* @param other
321-
* A JSONObject with which to compare this JSONObject
320+
* @param other JSONObject with which to compare this JSONObject
322321
*
323322
* @return True if the String representations are the same, False otherwise
324323
*

src/main/java/core/JSONStringifier.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@ public static String stringifyJson(JSONObject json) {
4242
public static String stringifyValue(Object value, Class<?> type) {
4343
if (type.equals(JSONObject.class)) {
4444
@SuppressWarnings("unchecked") // The type of this object is known from the type parameter
45-
ArrayList<JSONObject> objects = (ArrayList<JSONObject>) value;
45+
List<JSONObject> objects = (List<JSONObject>) value;
4646
StringBuilder result = new StringBuilder();
4747
for (JSONObject object : objects)
4848
result.append(stringifyObject(object));
4949
return result.toString();
5050
}
5151
if (type.equals(ArrayList.class)) {
52-
return stringifyArray((ArrayList<?>) value);
52+
return stringifyArray((List<?>) value);
5353
}
5454
return stringifyValue(value);
5555
}
@@ -74,9 +74,9 @@ public static String stringifyObject(JSONObject object) {
7474

7575
if (value == null)
7676
sb.append("null");
77-
else if (type != null && type.equals(JSONObject.class)) {
77+
else if (type != null && type.equals(JSONObject.class) && value instanceof List) {
7878
@SuppressWarnings("unchecked")
79-
List<JSONObject> objects = (ArrayList<JSONObject>) value;
79+
List<JSONObject> objects = (List<JSONObject>) value;
8080
sb.append("{");
8181
for (int i = 0; i < objects.size(); i++) {
8282
String nested = stringifyObject(objects.get(i));
@@ -85,6 +85,11 @@ else if (type != null && type.equals(JSONObject.class)) {
8585
}
8686
sb.append("}");
8787
}
88+
else if (type != null && value instanceof JSONObject json) {
89+
sb.append("{");
90+
sb.append(json.toString());
91+
sb.append("}");
92+
}
8893
else if (type != null)
8994
sb.append(stringifyValue(value, type));
9095
else

src/main/java/core/Jsonic.java

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package core;
2+
3+
import java.lang.reflect.Array;
4+
import java.lang.reflect.Modifier;
5+
import java.util.ArrayList;
6+
import java.util.Collection;
7+
import java.util.List;
8+
9+
/**
10+
* Interface allowing Json methods to be used to create and store JSONObjects
11+
* representing instances of classes which implement this interface.
12+
* <p>
13+
* <b> Example Usage: </b>
14+
* <p>
15+
* <code> class MyClass implements Jsonic&lt;MyClass&gt; { ... } </code>
16+
* @param <T> The type of the object implementing this interface (should be the same as the Class Name).
17+
*/
18+
public interface Jsonic<T extends Jsonic<T>> {
19+
20+
/**
21+
* Turn this object's fields into an accurately-modeled JSONObject.
22+
* @return A JSONObject representing this object.
23+
*/
24+
public JSONObject toJson();
25+
26+
/**
27+
* Interpret a JSONObject into an object of this type.
28+
* @param json A JSONObject containing any number of the fields for this object as key-value pairs.
29+
* @return The Object interpreted from the Json. Note that this object's fields will also be set.
30+
*/
31+
public T fromJson(JSONObject json);
32+
33+
/**
34+
* Turns a collection into a JSONObject list. Any elements which extend Jsonic<> will use their {@code .toJson()} methods,
35+
* and any other not-null elements will be passed to the {@code Jsonic.toJson(Object)} function.
36+
* @param collection Collection containing any type.
37+
* @return List of JSONObjects with the contents of the passed collection.
38+
* @see #toJson()
39+
* @see #toJson(Object)
40+
*/
41+
public static List<JSONObject> collectionToJson(Collection<?> collection) {
42+
List<JSONObject> json = new ArrayList<>();
43+
for (Object item : collection) {
44+
if (item instanceof Jsonic<?> jsonic) {
45+
json.add(jsonic.toJson());
46+
}
47+
else if (item != null) {
48+
json.add(toJson(item));
49+
}
50+
else json.add(new JSONObject()); // empty object representing null
51+
}
52+
return json;
53+
}
54+
55+
/**
56+
* Turns an array into a JSONObject list. Any elements which extend Jsonic<> will use their {@code .toJson()} methods,
57+
* and any other not-null elements will be passed to the {@code Jsonic.toJson(Object)} function.
58+
* @param array Array containing any type.
59+
* @return List of JSONObjects with the contents of the passed array.
60+
* @see #toJson()
61+
* @see #toJson(Object)
62+
*/
63+
public static List<JSONObject> arrayToJson(Object array) {
64+
int length = Array.getLength(array);
65+
List<JSONObject> json = new ArrayList<>();
66+
for (int i = 0; i < length; i++) {
67+
Object element = Array.get(array, i);
68+
if (element instanceof Jsonic<?> jsonic) {
69+
json.add(jsonic.toJson());
70+
}
71+
else if (element != null) {
72+
json.add(toJson(element));
73+
}
74+
}
75+
return json;
76+
}
77+
78+
private static boolean isPrimitiveOrWrapper(Class<?> type) {
79+
Class<?>[] types = {Boolean.class, Byte.class, Character.class, Short.class, Integer.class, Long.class, Float.class, Double.class, String.class};
80+
if (type.isPrimitive()) return true;
81+
for (Class<?> t : types) {
82+
if (type == t) return true;
83+
}
84+
return false;
85+
}
86+
87+
/**
88+
* Reflects the passed Class's fields into an unvalued "template" JSONObject.
89+
* @param clazz Any class.
90+
* @return A JSONObject "template" of the class's fields.
91+
* @see #toJson(Object)
92+
*/
93+
public static JSONObject classJson(Class<? extends Object> clazz) {
94+
List<JSONObject> fields = new ArrayList<>();
95+
for (java.lang.reflect.Field f : clazz.getDeclaredFields()) {
96+
// Only take public, non-static fields
97+
int modifiers = f.getModifiers();
98+
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
99+
String field = f.getName();
100+
fields.add(new JSONObject(field));
101+
}
102+
}
103+
return new JSONObject(clazz.getSimpleName(), fields);
104+
}
105+
106+
/**
107+
* Reflects the passed Object's public instance fields and their values into a JSONObject.
108+
* @param o Object of any type.
109+
* @return JSONObject containing the passed object's fields and their values.
110+
* @see #classJson(Class)
111+
*/
112+
public static JSONObject toJson(Object o) {
113+
List<JSONObject> fields = new ArrayList<>();
114+
Class<?> clazz = o.getClass();
115+
116+
// Get all fields declared by O.class
117+
for (java.lang.reflect.Field f : clazz.getDeclaredFields()) {
118+
// Only take public, non-static fields
119+
int modifiers = f.getModifiers();
120+
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
121+
Object value;
122+
try {
123+
value = f.get(o);
124+
}
125+
catch (IllegalArgumentException e) {
126+
e.printStackTrace();
127+
continue;
128+
}
129+
catch (IllegalAccessException e) {
130+
e.printStackTrace();
131+
continue;
132+
}
133+
134+
if (value instanceof Jsonic<?> jsonic) {
135+
fields.add(new JSONObject(f.getName(), jsonic.toJson()));
136+
}
137+
else if (value != null && isPrimitiveOrWrapper(value.getClass())) {
138+
fields.add(new JSONObject(f.getName(), value));
139+
}
140+
else if (value != null && value.getClass().isArray()) {
141+
fields.add(new JSONObject(f.getName(), arrayToJson(value)));
142+
}
143+
else if (value != null && value instanceof Collection<?> collection) {
144+
fields.add(new JSONObject(f.getName(), collectionToJson(collection)));
145+
}
146+
else if (value != null) {
147+
fields.add(new JSONObject(f.getName(), toJson(value)));
148+
}
149+
else fields.add(new JSONObject(f.getName(), null));
150+
}
151+
}
152+
return new JSONObject(o.getClass().getSimpleName(), fields);
153+
}
154+
}

0 commit comments

Comments
 (0)