Java to Json validation using GSON - java

While converting Java object to Json string using GSON API, I also want to fail this Json conversion if any of the annotated attribute is null.
For example
public class Order{
#SerializedName("orderId")
#Expose
#Required
private Integer id;
//getter & setter available for id
}
Now as I am doing
Order order = new Order();
JSONObject jsonobj = new JSONObject(gson.toJson(order));
I want to fail the above Java to Json transformation if any of the #Required attribute is null
Is this possible using GSON?

I wanted to fail Java to Json conversion, if any of the Java attribute is null which is annotated as #Required,
I am able to achieve this using following approach. Please let me know if you see any issues:
class RequiredKeyAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
return new TypeAdapter<T>() {
#Override
public void write(JsonWriter out, T value) throws IOException {
if (value != null) {
Field[] fields = value.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (fields[i]
.isAnnotationPresent(Required.class)) {
validateNullValue(value, fields[i]);
}
}
}
delegate.write(out, value);
}
private <T> void validateNullValue(T value, Field field) {
field.setAccessible(true);
Class t = field.getType();
Object v = null;
try {
v = field.get(value);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
if (t == boolean.class && Boolean.FALSE.equals(v)) {
throw new IllegalArgumentException(field + " is null");
} else if (t.isPrimitive()
&& ((Number) v).doubleValue() == 0) {
throw new IllegalArgumentException(field + " is null");
} else if (!t.isPrimitive() && v == null) {
throw new IllegalArgumentException(field + " is null");
}
}
#Override
public T read(JsonReader in) throws IOException {
return delegate.read(in);
}
};
}
}
RequiredKeyAdapterFactory requiredKeyAdapterFactory = new RequiredKeyAdapterFactory();
Gson gson = new GsonBuilder().registerTypeAdapterFactory(requiredKeyAdapterFactory)
.create();
This is working

Related

Jackson ContextualDeserializer does not deserialize all fields

I'm implementing a custom jackson deserializer for one of my entities.
My entity is the following:
#Value
#JsonDeserialize
#AllArgsConstructor
public class TestModel {
private final FieldUpdate<UUID> field1Update;
private final FieldUpdate<UUID> field2Update;
private final FieldUpdate<String> field3Update;
public String toString() {
return "TestModel. Field1="+(field1Update != null ? field1Update.toString() : null)+
" Field2="+(field2Update != null ? field2Update.getClass().getName() : null) +
" Field3="+(field3Update != null ? field3Update.getClass().getName() : null);
}
}
My problem is that serialiation works as expected - the successfully serialized object is as follow:
{
"field1Update" : {
"type" : "update",
"value" : "f59c4ef9-52c4-4f3d-99e5-a33a13ae12f3"
},
"field2Update" : {
"type" : "keep"
},
"field3Update" : {
"type" : "reset"
}
}
=> which is correct.
(There are the 3 Types Update, Keep and Reset). Only update needs a value.
The problem is: When i deserialize this, only the first field (field1Update) gets deserialized. The other 2 fields (field2Update and field3Update) are null after deserialization completes.
My Deserializer is the following:
public class FieldUpdateDeserializer extends StdDeserializer implements ContextualDeserializer {
private JavaType contentType;
public FieldUpdateDeserializer(JavaType contentType) {
this(null,contentType);
}
public FieldUpdateDeserializer() {
this(null,null);
}
public FieldUpdateDeserializer(Class<?> vc, JavaType contentType) {
super(vc);
this.contentType = contentType;
}
public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
BeanProperty property) throws JsonMappingException {
JavaType t = property.getType();
JavaType boundType = t.getBindings().getBoundType(0);
return new FieldUpdateDeserializer(boundType);
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
if(!"type".equals(jp.nextFieldName()) )throw new JsonParseException(jp,"'type' expected");
String typeVal = jp.nextTextValue();
if("update".equals(typeVal)) {
jp.nextValue(); //consume type.
try {
JsonDeserializer deser = ctx.findNonContextualValueDeserializer(contentType);
return new Update<>(deser.deserialize(jp,ctx));
} catch (Exception ex) {
throw new IllegalStateException("Could not handle deserialization for type", ex);
}
} else if("keep".equals(typeVal)) {
return new Keep<>();
} else if("reset".equals(typeVal)) {
return new Reset<>();
} else {
return ctx.handleUnexpectedToken(FieldUpdate.class, jp);
}
}
}
An interesting fact is that jackson calls the deserialize(...) method only one time and i can't figure out why....
Glad if somebody can drop me a hint.
greetings,
Michael
Ok - after some sleep and analyzing what happens in the jackson serializer, i discovered that i did not consume enough tokens in my deserializer.
The working version for my deserializer is:
public Object deserialize(JsonParser jp, DeserializationContext ctx) throws IOException {
if(!"type".equals(jp.nextFieldName()) )throw new JsonParseException(jp,"'type' expected");
String typeVal = jp.nextTextValue();
if("update".equals(typeVal)) {
jp.nextValue(); //consume type.
try {
JsonDeserializer deser = ctx.findNonContextualValueDeserializer(contentType);
return new Update<>(deser.deserialize(jp,ctx));
} catch (Exception ex) {
throw new IllegalStateException("Could not handle deserialization for type", ex);
} finally {
jp.nextToken();
}
} else if("keep".equals(typeVal)) {
jp.nextToken();
return new Keep<>();
} else if("reset".equals(typeVal)) {
jp.nextToken();
return new Reset<>();
} else {
return ctx.handleUnexpectedToken(FieldUpdate.class, jp);
}
}

JavaFX TreeView JSON Ex/Import via GSON

I´m looking for a way to export a JavaFX TreeView to JSON. To make this whole process simple, I use GSON. Its exporting the value of a treeItem well, but when I try to use the whole Tree its ending in a stack overflow. I believe this has something to do with the parent/child attribute. Is there a way to prevent GSON from exporting this attribute.
And how do I import the whole thing again? I wasn't able to import a simple object of mine, because GSON can't handle Properties.
You need to use a custom type adapter. Furthermore you can prevent stackoverflows by using loops instead of recursion:
public class TreeItemTypeAdapter<T> extends TypeAdapter<TreeItem<T>> {
private Gson gson;
public void setGson(Gson gson) {
this.gson = gson;
}
private final Class<T> valueClass;
public TreeItemTypeAdapter(Class<T> valueClass) {
if (valueClass == null) {
throw new IllegalArgumentException();
}
this.valueClass = valueClass;
}
public static TreeItemTypeAdapter<String> createStringTreeItemAdapter() {
return new TreeItemTypeAdapter<>(String.class);
}
private void writeValue(JsonWriter writer, T t) throws IOException {
if (gson == null) {
writer.value(Objects.toString(t, null));
} else {
gson.toJson(t, valueClass, writer);
}
}
private T readValue(JsonReader reader) throws IOException {
if (gson == null) {
Object value = reader.nextString();
return (T) value;
} else {
return gson.fromJson(reader, valueClass);
}
}
#Override
public void write(JsonWriter writer, TreeItem<T> t) throws IOException {
writer.beginObject().name("value");
writeValue(writer, t.getValue());
writer.name("children").beginArray();
LinkedList<Iterator<TreeItem<T>>> iterators = new LinkedList<>();
iterators.add(t.getChildren().iterator());
while (!iterators.isEmpty()) {
Iterator<TreeItem<T>> last = iterators.peekLast();
if (last.hasNext()) {
TreeItem<T> ti = last.next();
writer.beginObject().name("value");
writeValue(writer, ti.getValue());
writer.name("children").beginArray();
iterators.add(ti.getChildren().iterator());
} else {
writer.endArray().endObject();
iterators.pollLast();
}
}
}
#Override
public TreeItem<T> read(JsonReader reader) throws IOException {
if (gson == null && !valueClass.getName().equals("java.lang.String")) {
throw new IllegalStateException("cannot parse classes other than String without gson provided");
}
reader.beginObject();
if (!"value".equals(reader.nextName())) {
throw new IOException("value expected");
}
TreeItem<T> root = new TreeItem<>(readValue(reader));
TreeItem<T> item = root;
if (!"children".equals(reader.nextName())) {
throw new IOException("children expected");
}
reader.beginArray();
int depth = 1;
while (depth > 0) {
if (reader.hasNext()) {
reader.beginObject();
if (!"value".equals(reader.nextName())) {
throw new IOException("value expected");
}
TreeItem<T> newItem = new TreeItem<>(readValue(reader));
item.getChildren().add(newItem);
item = newItem;
if (!"children".equals(reader.nextName())) {
throw new IOException("children expected");
}
reader.beginArray();
depth++;
} else {
depth--;
reader.endArray();
reader.endObject();
item = item.getParent();
}
}
return root;
}
}
public static void main(String[] args) {
TreeItem<String> ti = new TreeItem<>("Hello world");
TreeItem<String> ti2 = new TreeItem<>("42");
TreeItem<String> ti3 = new TreeItem<>("Foo");
TreeItem<String> ti4 = new TreeItem<>("Bar");
ti.getChildren().addAll(ti2, ti3);
ti2.getChildren().add(ti4);
TreeItemTypeAdapter<String> adapter = new TreeItemTypeAdapter<>(String.class);
Gson gson = new GsonBuilder().registerTypeAdapter(TreeItem.class, adapter).create();
adapter.setGson(gson);
System.out.println(gson.toJson(ti));
System.out.println(toString(gson.fromJson("{\"value\":\"Hello world\",\"children\":[{\"value\":\"42\",\"children\":[{\"value\":\"Bar\",\"children\":[]}]},{\"value\":\"Foo\",\"children\":[]}]}",
TreeItem.class)));
}
private static String toString(TreeItem ti) {
StringBuilder sb = new StringBuilder("TreeItem [ value: \"").append(ti.getValue()).append("\" children [");
boolean notFirst = false;
for (TreeItem i : (List<TreeItem>) ti.getChildren()) {
if (notFirst) {
sb.append(",");
} else {
notFirst = true;
}
sb.append(toString(i));
}
return sb.append("]]").toString();
}

Get single field from JSON using Jackson

Given an arbitrary JSON I would like to get value of a single field contentType. How to do it with Jackson?
{
contentType: "foo",
fooField1: ...
}
{
contentType: "bar",
barArray: [...]
}
Related
How to find specified name and its value in JSON-string from Java? (GSON)
Using gson to deserialize specific JSON field of an object (GSON)
The Jackson Way
Considering that you don't have a POJO describing your data structure, you could simply do:
final String json = "{\"contentType\": \"foo\", \"fooField1\": ... }";
final ObjectNode node = new ObjectMapper().readValue(json, ObjectNode.class);
// ^
// actually, try and *reuse* a single instance of ObjectMapper
if (node.has("contentType")) {
System.out.println("contentType: " + node.get("contentType"));
}
Addressing concerns in the comments section
If, however, you wish to not consume the entire source String, but simply access a specific property whose path you know, you'll have to write it yourself, leveraging a Tokeniser.
Actually, it's the weekend and I got time on my hands, so I could give you a head start: here's a basic one! It can run in strict mode and spew out sensible error messages, or be lenient and return Optional.empty when the request couldn't be fulfilled.
public static class JSONPath {
protected static final JsonFactory JSON_FACTORY = new JsonFactory();
private final List<JSONKey> keys;
public JSONPath(final String from) {
this.keys = Arrays.stream((from.startsWith("[") ? from : String.valueOf("." + from))
.split("(?=\\[|\\]|\\.)"))
.filter(x -> !"]".equals(x))
.map(JSONKey::new)
.collect(Collectors.toList());
}
public Optional<String> getWithin(final String json) throws IOException {
return this.getWithin(json, false);
}
public Optional<String> getWithin(final String json, final boolean strict) throws IOException {
try (final InputStream stream = new StringInputStream(json)) {
return this.getWithin(stream, strict);
}
}
public Optional<String> getWithin(final InputStream json) throws IOException {
return this.getWithin(json, false);
}
public Optional<String> getWithin(final InputStream json, final boolean strict) throws IOException {
return getValueAt(JSON_FACTORY.createParser(json), 0, strict);
}
protected Optional<String> getValueAt(final JsonParser parser, final int idx, final boolean strict) throws IOException {
try {
if (parser.isClosed()) {
return Optional.empty();
}
if (idx >= this.keys.size()) {
parser.nextToken();
if (null == parser.getValueAsString()) {
throw new JSONPathException("The selected node is not a leaf");
}
return Optional.of(parser.getValueAsString());
}
this.keys.get(idx).advanceCursor(parser);
return getValueAt(parser, idx + 1, strict);
} catch (final JSONPathException e) {
if (strict) {
throw (null == e.getCause() ? new JSONPathException(e.getMessage() + String.format(", at path: '%s'", this.toString(idx)), e) : e);
}
return Optional.empty();
}
}
#Override
public String toString() {
return ((Function<String, String>) x -> x.startsWith(".") ? x.substring(1) : x)
.apply(this.keys.stream().map(JSONKey::toString).collect(Collectors.joining()));
}
private String toString(final int idx) {
return ((Function<String, String>) x -> x.startsWith(".") ? x.substring(1) : x)
.apply(this.keys.subList(0, idx).stream().map(JSONKey::toString).collect(Collectors.joining()));
}
#SuppressWarnings("serial")
public static class JSONPathException extends RuntimeException {
public JSONPathException() {
super();
}
public JSONPathException(final String message) {
super(message);
}
public JSONPathException(final String message, final Throwable cause) {
super(message, cause);
}
public JSONPathException(final Throwable cause) {
super(cause);
}
}
private static class JSONKey {
private final String key;
private final JsonToken startToken;
public JSONKey(final String str) {
this(str.substring(1), str.startsWith("[") ? JsonToken.START_ARRAY : JsonToken.START_OBJECT);
}
private JSONKey(final String key, final JsonToken startToken) {
this.key = key;
this.startToken = startToken;
}
/**
* Advances the cursor until finding the current {#link JSONKey}, or
* having consumed the entirety of the current JSON Object or Array.
*/
public void advanceCursor(final JsonParser parser) throws IOException {
final JsonToken token = parser.nextToken();
if (!this.startToken.equals(token)) {
throw new JSONPathException(String.format("Expected token of type '%s', got: '%s'", this.startToken, token));
}
if (JsonToken.START_ARRAY.equals(this.startToken)) {
// Moving cursor within a JSON Array
for (int i = 0; i != Integer.valueOf(this.key).intValue(); i++) {
JSONKey.skipToNext(parser);
}
} else {
// Moving cursor in a JSON Object
String name;
for (parser.nextToken(), name = parser.getCurrentName(); !this.key.equals(name); parser.nextToken(), name = parser.getCurrentName()) {
JSONKey.skipToNext(parser);
}
}
}
/**
* Advances the cursor to the next entry in the current JSON Object
* or Array.
*/
private static void skipToNext(final JsonParser parser) throws IOException {
final JsonToken token = parser.nextToken();
if (JsonToken.START_ARRAY.equals(token) || JsonToken.START_OBJECT.equals(token) || JsonToken.FIELD_NAME.equals(token)) {
skipToNextImpl(parser, 1);
} else if (JsonToken.END_ARRAY.equals(token) || JsonToken.END_OBJECT.equals(token)) {
throw new JSONPathException("Could not find requested key");
}
}
/**
* Recursively consumes whatever is next until getting back to the
* same depth level.
*/
private static void skipToNextImpl(final JsonParser parser, final int depth) throws IOException {
if (depth == 0) {
return;
}
final JsonToken token = parser.nextToken();
if (JsonToken.START_ARRAY.equals(token) || JsonToken.START_OBJECT.equals(token) || JsonToken.FIELD_NAME.equals(token)) {
skipToNextImpl(parser, depth + 1);
} else {
skipToNextImpl(parser, depth - 1);
}
}
#Override
public String toString() {
return String.format(this.startToken.equals(JsonToken.START_ARRAY) ? "[%s]" : ".%s", this.key);
}
}
}
Assuming the following JSON content:
{
"people": [{
"name": "Eric",
"age": 28
}, {
"name": "Karin",
"age": 26
}],
"company": {
"name": "Elm Farm",
"address": "3756 Preston Street Wichita, KS 67213",
"phone": "857-778-1265"
}
}
... you could use my JSONPath class as follows:
final String json = "{\"people\":[],\"company\":{}}"; // refer to JSON above
System.out.println(new JSONPath("people[0].name").getWithin(json)); // Optional[Eric]
System.out.println(new JSONPath("people[1].name").getWithin(json)); // Optional[Karin]
System.out.println(new JSONPath("people[2].name").getWithin(json)); // Optional.empty
System.out.println(new JSONPath("people[0].age").getWithin(json)); // Optional[28]
System.out.println(new JSONPath("company").getWithin(json)); // Optional.empty
System.out.println(new JSONPath("company.name").getWithin(json)); // Optional[Elm Farm]
Keep in mind that it's basic. It doesn't coerce data types (every value it returns is a String) and only returns leaf nodes.
Actual test case
It handles InputStreams, so you can test it against some giant JSON document and see that it's much faster than it would take your browser to download and display its contents:
System.out.println(new JSONPath("info.contact.email")
.getWithin(new URL("http://test-api.rescuegroups.org/v5/public/swagger.php").openStream()));
// Optional[support#rescuegroups.org]
Quick test
Note I'm not re-using any already existing JSONPath or ObjectMapper so the results are inaccurate -- this is just a very rough comparison anyways:
public static Long time(final Callable<?> r) throws Exception {
final long start = System.currentTimeMillis();
r.call();
return Long.valueOf(System.currentTimeMillis() - start);
}
public static void main(final String[] args) throws Exception {
final URL url = new URL("http://test-api.rescuegroups.org/v5/public/swagger.php");
System.out.println(String.format( "%dms to get 'info.contact.email' with JSONPath",
time(() -> new JSONPath("info.contact.email").getWithin(url.openStream()))));
System.out.println(String.format( "%dms to just download the entire document otherwise",
time(() -> new Scanner(url.openStream()).useDelimiter("\\A").next())));
System.out.println(String.format( "%dms to bluntly map it entirely with Jackson and access a specific field",
time(() -> new ObjectMapper()
.readValue(url.openStream(), ObjectNode.class)
.get("info").get("contact").get("email"))));
}
378ms to get 'info.contact.email' with JSONPath
756ms to just download the entire document otherwise
896ms to bluntly map it entirely with Jackson and access a specific field
Just want to update for 2019. I found the following easiest to impl:
//json can be file or String
JsonNode parent= new ObjectMapper().readTree(json);
String content = parent.path("contentType").asText();
I would suggest to use path instead of get as get throws a NPE, where path returns with a default 0 or "", which is safer to work with if setting up the parsing correctly for 1st time.
My $0.02
If you are using JSON jars in your application then the following code snippet is useful:
String json = "{\"contentType\": \"foo\", \"fooField1\": ... }";
JSONObject jsonObject = new JSONObject(json);
System.out.println(jsonObject.getString("contentType"));
and if you are using Gson jars then the same code will look like following:
Gson gson = new GsonBuilder().create();
Map jsonMap = gson.fromJson(json, Map.class);
System.out.println(jsonMap.get("contentType"));
Another way is:
String json = "{\"contentType\": \"foo\", \"fooField1\": ... }";
JsonNode parent= new ObjectMapper().readTree(json);
String content = parent.get("contentType").asText();
I faced this issue when I decided to use Jackson as the json library for a project I worked on; mainly for its speed. I was already used to using org.json and Gson for my projects.
I quickly found out though that many tasks that were trivial in org.json and Gson were not so straightforward in Jackson
So I wrote the following classes to make things easier for me.
The classes below will allow you to use Jackson as easily as you would the simple org.json library, while still retaining the power and speed of Jackson
I wrote the whole thing in a few hours, so feel free to debug and suit the code to your own purposes.
Note that JSONObject/JSONArray below will do exactly what the OP wants.
The first is JSONObject which has similar methods to org.json.JSONObject; but at heart runs Jackson code to build JSON and parse json strings.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* #author GBEMIRO JIBOYE <gbenroscience#gmail.com>
*/
public class JSONObject {
ObjectNode parseNode;
public JSONObject() {
this.parseNode = JsonNodeFactory.instance.objectNode(); // initializing
}
public JSONObject(String json) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
try {
this.parseNode = mapper.readValue(json, ObjectNode.class);
} catch (JsonProcessingException ex) {
Logger.getLogger(JSONObject.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void put(String key, String value) {
parseNode.put("key", value); // building
}
public void put(String key, boolean value) {
parseNode.put("key", value); // building
}
public void put(String key, int value) {
parseNode.put("key", value); // building
}
public void put(String key, short value) {
parseNode.put("key", value); // building
}
public void put(String key, float value) {
parseNode.put("key", value); // building
}
public void put(String key, long value) {
parseNode.put("key", value); // building
}
public void put(String key, double value) {
parseNode.put("key", value); // building
}
public void put(String key, byte[] value) {
parseNode.put("key", value); // building
}
public void put(String key, BigInteger value) {
parseNode.put("key", value); // building
}
public void put(String key, BigDecimal value) {
parseNode.put("key", value); // building
}
public void put(String key, Object[] value) {
ArrayNode anode = parseNode.putArray(key);
for (Object o : value) {
anode.addPOJO(o); // building
}
}
public void put(String key, JSONObject value) {
parseNode.set(key, value.parseNode);
}
public void put(String key, Object value) {
parseNode.putPOJO(key, value);
}
public static class Parser<T> {
public T decode(String json, Class clazz) {
try {
return new Converter<T>().fromJsonString(json, clazz);
} catch (IOException ex) {
Logger.getLogger(JSONObject.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}
public int optInt(String key) {
if (parseNode != null) {
JsonNode nod = parseNode.get(key);
return nod != null ? nod.asInt(0) : 0;
}
return 0;
}
public long optLong(String key) {
if (parseNode != null) {
JsonNode nod = parseNode.get(key);
return nod != null ? nod.asLong(0) : 0;
}
return 0;
}
public double optDouble(String key) {
if (parseNode != null) {
JsonNode nod = parseNode.get(key);
return nod != null ? nod.asDouble(0) : 0;
}
return 0;
}
public boolean optBoolean(String key) {
if (parseNode != null) {
JsonNode nod = parseNode.get(key);
return nod != null ? nod.asBoolean(false) : false;
}
return false;
}
public double optFloat(String key) {
if (parseNode != null) {
JsonNode nod = parseNode.get(key);
return nod != null && nod.isFloat() ? nod.floatValue() : 0;
}
return 0;
}
public short optShort(String key) {
if (parseNode != null) {
JsonNode nod = parseNode.get(key);
return nod != null && nod.isShort() ? nod.shortValue() : 0;
}
return 0;
}
public byte optByte(String key) {
if (parseNode != null) {
JsonNode nod = parseNode.get(key);
return nod != null && nod.isShort() ? (byte) nod.asInt(0) : 0;
}
return 0;
}
public JSONObject optJSONObject(String key) {
if (parseNode != null) {
if (parseNode.has(key)) {
ObjectNode nod = parseNode.with(key);
JSONObject obj = new JSONObject();
obj.parseNode = nod;
return obj;
}
}
return new JSONObject();
}
public JSONArray optJSONArray(String key) {
if (parseNode != null) {
if (parseNode.has(key)) {
ArrayNode nod = parseNode.withArray(key);
JSONArray obj = new JSONArray();
if (nod != null) {
obj.parseNode = nod;
return obj;
}
}
}
return new JSONArray();
}
public String optString(String key) {
if (parseNode != null) {
JsonNode nod = parseNode.get(key);
return parseNode != null && nod.isTextual() ? nod.asText("") : "";
}
return "";
}
#Override
public String toString() {
return parseNode.toString();
}
public String toCuteString() {
return parseNode.toPrettyString();
}
}
Here is the code for the JSONArray equivalent that works like org.json.JSONArray; but uses Jackson code.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
*
* #author GBEMIRO JIBOYE <gbenroscience#gmail.com>
*/
public class JSONArray {
protected ArrayNode parseNode;
public JSONArray() {
this.parseNode = JsonNodeFactory.instance.arrayNode(); // initializing
}
public JSONArray(String json) throws JsonProcessingException{
ObjectMapper mapper = new ObjectMapper();
this.parseNode = mapper.readValue(json, ArrayNode.class);
}
public void putByte(byte val) {
parseNode.add(val);
}
public void putShort(short val) {
parseNode.add(val);
}
public void put(int val) {
parseNode.add(val);
}
public void put(long val) {
parseNode.add(val);
}
public void pu(float val) {
parseNode.add(val);
}
public void put(double val) {
parseNode.add(val);
}
public void put(String val) {
parseNode.add(val);
}
public void put(byte[] val) {
parseNode.add(val);
}
public void put(BigDecimal val) {
parseNode.add(val);
}
public void put(BigInteger val) {
parseNode.add(val);
}
public void put(Object val) {
parseNode.addPOJO(val);
}
public void put(int index, JSONArray value) {
parseNode.set(index, value.parseNode);
}
public void put(int index, JSONObject value) {
parseNode.set(index, value.parseNode);
}
public String optString(int index) {
if (parseNode != null) {
JsonNode nod = parseNode.get(index);
return nod != null ? nod.asText("") : "";
}
return "";
}
public int optInt(int index) {
if (parseNode != null) {
JsonNode nod = parseNode.get(index);
return nod != null ? nod.asInt(0) : 0;
}
return 0;
}
public long optLong(int index) {
if (parseNode != null) {
JsonNode nod = parseNode.get(index);
return nod != null ? nod.asLong(0) : 0;
}
return 0;
}
public double optDouble(int index) {
if (parseNode != null) {
JsonNode nod = parseNode.get(index);
return nod != null ? nod.asDouble(0) : 0;
}
return 0;
}
public boolean optBoolean(int index) {
if (parseNode != null) {
JsonNode nod = parseNode.get(index);
return nod != null ? nod.asBoolean(false) : false;
}
return false;
}
public double optFloat(int index) {
if (parseNode != null) {
JsonNode nod = parseNode.get(index);
return nod != null && nod.isFloat() ? nod.floatValue() : 0;
}
return 0;
}
public short optShort(int index) {
if (parseNode != null) {
JsonNode nod = parseNode.get(index);
return nod != null && nod.isShort() ? nod.shortValue() : 0;
}
return 0;
}
public byte optByte(int index) {
if (parseNode != null) {
JsonNode nod = parseNode.get(index);
return nod != null && nod.isShort() ? (byte) nod.asInt(0) : 0;
}
return 0;
}
public JSONObject optJSONObject(int index) {
if (parseNode != null) {
JsonNode nod = parseNode.get(index);
if(nod != null){
if(nod.isObject()){
ObjectNode obn = (ObjectNode) nod;
JSONObject obj = new JSONObject();
obj.parseNode = obn;
return obj;
}
}
}
return new JSONObject();
}
public JSONArray optJSONArray(int index) {
if (parseNode != null) {
JsonNode nod = parseNode.get(index);
if(nod != null){
if(nod.isArray()){
ArrayNode anode = (ArrayNode) nod;
JSONArray obj = new JSONArray();
obj.parseNode = anode;
return obj;
}
}
}
return new JSONArray();
}
#Override
public String toString() {
return parseNode.toString();
}
public String toCuteString() {
return parseNode.toPrettyString();
}
}
Finally for a one size-fits-all-most-likely for encoding and decoding your Java classes to JSON, I added this simple class:
/**
*
* #author GBEMIRO JIBOYE <gbenroscience#gmail.com>
*/
public class Converter<T> {
// Serialize/deserialize helpers
private Class clazz;
public Converter() {}
public T fromJsonString(String json , Class clazz) throws IOException {
this.clazz = clazz;
return getObjectReader().readValue(json);
}
public String toJsonString(T obj) throws JsonProcessingException {
this.clazz = obj.getClass();
return getObjectWriter().writeValueAsString(obj);
}
private ObjectReader requestReader;
private ObjectWriter requestWriter;
private void instantiateMapper() {
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
requestReader = mapper.readerFor(clazz);
requestWriter = mapper.writerFor(clazz);
}
private ObjectReader getObjectReader() {
if (requestReader == null) {
instantiateMapper();
}
return requestReader;
}
private ObjectWriter getObjectWriter() {
if (requestWriter == null) {
instantiateMapper();
}
return requestWriter;
}
}
Now to taste(test) the sauce(code), use the following methods:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* #author GBEMIRO JIBOYE <gbenroscience#gmail.com>
*/
public class SimplerJacksonTest {
static class Credentials {
private String userName;
private String uid;
private String password;
private long createdAt;
public Credentials() {
}
public Credentials(String userName, String uid, String password, long createdAt) {
this.userName = userName;
this.uid = uid;
this.password = password;
this.createdAt = createdAt;
}
#JsonProperty("userName")
public String getUserName() {
return userName;
}
#JsonProperty("userName")
public void setUserName(String userName) {
this.userName = userName;
}
#JsonProperty("uid")
public String getUid() {
return uid;
}
#JsonProperty("uid")
public void setUid(String uid) {
this.uid = uid;
}
#JsonProperty("password")
public String getPassword() {
return password;
}
#JsonProperty("password")
public void setPassword(String password) {
this.password = password;
}
#JsonProperty("createdAt")
public long getCreatedAt() {
return createdAt;
}
#JsonProperty("createdAt")
public void setCreatedAt(long createdAt) {
this.createdAt = createdAt;
}
public String encode() {
try {
return new Converter<Credentials>().toJsonString(this);
} catch (JsonProcessingException ex) {
Logger.getLogger(Credentials.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
public Credentials decode(String jsonData) {
try {
return new Converter<Credentials>().fromJsonString(jsonData, Credentials.class);
} catch (Exception ex) {
Logger.getLogger(Converter.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}
public static JSONObject testJSONObjectBuild() {
JSONObject obj = new JSONObject();
Credentials cred = new Credentials("Adesina", "01eab26bwkwjbak2vngxh9y3q6", "xxxxxx1234", System.currentTimeMillis());
String arr[] = new String[]{"Boy", "Girl", "Man", "Woman"};
int nums[] = new int[]{0, 1, 2, 3, 4, 5};
obj.put("creds", cred);
obj.put("pronouns", arr);
obj.put("creds", cred);
obj.put("nums", nums);
System.out.println("json-coding: " + obj.toCuteString());
return obj;
}
public static void testJSONObjectParse(String json) {
JSONObject obj;
try {
obj = new JSONObject(json);
JSONObject credsObj = obj.optJSONObject("creds");
String userName = credsObj.optString("userName");
String uid = credsObj.optString("uid");
String password = credsObj.optString("password");
long createdAt = credsObj.optLong("createdAt");
System.out.println("<<---Parse Results--->>");
System.out.println("userName = " + userName);
System.out.println("uid = " + uid);
System.out.println("password = " + password);
System.out.println("createdAt = " + createdAt);
} catch (JsonProcessingException ex) {
Logger.getLogger(JSONObject.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static JSONArray testJSONArrayBuild() {
JSONArray array = new JSONArray();
array.put(new Credentials("Lawani", "001uadywdbs", "ampouehehu", System.currentTimeMillis()));
array.put("12");
array.put(98);
array.put(Math.PI);
array.put("Good scores!");
System.out.println("See the built array: "+array.toCuteString());
return array;
}
public static void testJSONArrayParse(String json) {
try {
JSONArray array = new JSONArray(json);
JSONObject credsObj = array.optJSONObject(0);
//Parse credentials in index 0
String userName = credsObj.optString("userName");
String uid = credsObj.optString("uid");
String password = credsObj.optString("password");
long createdAt = credsObj.optLong("createdAt");
//Now return to the main array and parse other entries
String twelve = array.optString(1);
int ninety = array.optInt(2);
double pi = array.optDouble(3);
String scoreNews = array.optString(4);
System.out.println("Parse Results");
System.out.println("userName = " + userName);
System.out.println("uid = " + uid);
System.out.println("password = " + password);
System.out.println("createdAt = " + createdAt);
System.out.println("Parse Results");
System.out.println("index 1 = " + twelve);
System.out.println("index 2 = " + ninety);
System.out.println("index 3 = " + pi);
System.out.println("index 4 = " + scoreNews);
} catch (JsonProcessingException ex) {
Logger.getLogger(JSONObject.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static String testCredentialsEncode(){
Credentials cred = new Credentials("Olaoluwa", "01eab26bwkwjbak2vngxh9y3q6", "xxxxxx1234", System.currentTimeMillis());
String encoded = cred.encode();
System.out.println("encoded credentials = "+encoded);
return encoded;
}
public static Credentials testCredentialsDecode(String json){
Credentials cred = new Credentials().decode(json);
System.out.println("encoded credentials = "+cred.encode());
return cred;
}
public static void main(String[] args) {
JSONObject jo = testJSONObjectBuild();
testJSONObjectParse(jo.toString());
JSONArray ja = testJSONArrayBuild();
testJSONArrayParse(ja.toString());
String credsJSON = testCredentialsEncode();
testCredentialsDecode(credsJSON);
}
}
To get the source code in a place, instead of having to copy the one here, see:
the code on Github

Java Reflection to set Value for the java pojo

Goal :
set value for the given java bean at run time and generate JSON Object or JSON array.
the above is my goal and i have tried some thing like the below :
package com.hexgen.tools;
import java.lang.reflect.Method;
import com.google.gson.Gson;
public class ConvertPOJOToJSON {
public Object creatJSONObject(String className) throws IllegalArgumentException, IllegalAccessException, InstantiationException, Exception {
Class<?> objectClass = null;
Object clsObject =null;
try {
objectClass = Class.forName(className);
clsObject = objectClass.newInstance();
for(Method m : objectClass.getMethods())
if (m.getName().startsWith("set") && m.getParameterTypes().length == 1 && m.getModifiers()==23)
m.invoke(clsObject, "myValue");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return clsObject;
}
public static void main(String[] args) {
Gson gson = new Gson();
ConvertPOJOToJSON pojoToJSON = new ConvertPOJOToJSON();
try {
System.out.println("JSON OBJECT : "+gson.toJson(pojoToJSON.creatJSONObject("com.hexgen.ro.request.CreateRequisitionRO")));
} catch (Exception e) {
e.printStackTrace();
}
}
}
The Output of the above class :
JSON OBJECT : {"isAllocable":false}
there are many fields in the class i gave com.hexgen.ro.request.CreateRequisitionRO but only one boolean value is set to false and returns the value.
i have some constant value to set for the fields say if the field type is Integer than set some default integer value like so
EDIT :
I have created a enum like the following :
package com.hexgen.tools;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.joda.time.LocalDate;
public enum DefaultParamValues {
STRING("HEXGEN"),
INTEGER(123),
DATE(new LocalDate()),
BOOLEAN(true),
LONGVALUE(123123),
BIGDECIMAL(new BigDecimal("100000"));
private String defaultString;
private int defaultInteger;
private LocalDate defaultDate;
private boolean defaultBoolean;
private long defaultLong;
private BigDecimal defaultBigDecimal;
private DefaultParamValues(String strDefaultValue) {
defaultString = strDefaultValue;
}
private DefaultParamValues(int intDefaultValue) {
defaultInteger = intDefaultValue;
}
private DefaultParamValues(LocalDate dateDefaultValue) {
defaultDate = dateDefaultValue;
}
private DefaultParamValues(boolean booleanDefaultValue) {
defaultBoolean = booleanDefaultValue;
}
private DefaultParamValues(long longDefaultValue) {
defaultLong = longDefaultValue;
}
private DefaultParamValues(BigDecimal bigIntegerDefaultValue) {
defaultBigDecimal = bigIntegerDefaultValue;
}
public String getDefaultString() {
return defaultString;
}
public int getDefaultInt() {
return defaultInteger;
}
public LocalDate getDefaultDate() {
return defaultDate;
}
public boolean getDefaultBoolean() {
return defaultBoolean;
}
public long getDefaultLong() {
return defaultLong;
}
public BigDecimal getDdefaultBigDecimal() {
return defaultBigDecimal;
}
}
created one more method like the following :
public Object creatObjectWithDefaultValue(String className) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
DefaultParamValues defaultParamValues = null;
Class<?> objectClass = null;
Object clsObject =null;
try {
objectClass = Class.forName(className);
clsObject = objectClass.newInstance();
Field[] fields = objectClass.getDeclaredFields();
for(Field f:fields){
if(!f.isAccessible()){
f.setAccessible(true);
Class<?> type = f.getType();
if(! Modifier.isFinal(f.getModifiers()) && type.equals(Integer.class)){
f.set(clsObject, defaultParamValues.INTEGER);
} else if( !Modifier.isFinal(f.getModifiers()) && type.equals(java.math.BigDecimal.class)){
f.set(clsObject, defaultParamValues.BIGDECIMAL);
} else if(! Modifier.isFinal(f.getModifiers()) && type.equals(org.joda.time.LocalDate.class)){
f.set(clsObject,defaultParamValues.DATE);
} else if(! Modifier.isFinal(f.getModifiers()) && type.equals(boolean.class)){
f.set(clsObject, defaultParamValues.BOOLEAN);
} else if(! Modifier.isFinal(f.getModifiers()) && type.equals(java.lang.String.class)){
f.set(clsObject, defaultParamValues.STRING);
}
else if(! Modifier.isFinal(f.getModifiers()) && type.equals(long.class)){
f.set(clsObject, defaultParamValues.LONGVALUE);
}
//f.setAccessible(false);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return clsObject;
}
to set the default values but i get the following exception:
Exception in thread "main" java.lang.IllegalArgumentException: Can not set java.math.BigDecimal field com.hexgen.ro.request.CreateRequisitionRO.transSrlNo to com.hexgen.tools.DefaultParamValues
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:146)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:150)
at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:63)
at java.lang.reflect.Field.set(Field.java:657)
at com.hexgen.tools.JsonConverter.creatObjectWithDefaultValue(JsonConverter.java:93)
at com.hexgen.tools.JsonConverter.main(JsonConverter.java:201)
Please help me to find the solution.
Best Regards
If your looking for a lightweight library which can do this and more including allowing you to do your own filtering to find Methods/Fields. I wrote an open source library which has no 3rd party dependencies and is available on Maven Central.
Checkout https://github.com/gondor/reflect
As for your issue it appears your setting the "Enum" constant and not the inner value of the enum. Wouldn't it DefaultParamValues.BIGDECIMAL.getDdefaultBigDecimal()

Jackson deserialization error handling

My problem is fairly simple : I have the following simple class:
public class Foo {
private int id = -1;
public void setId(int _id){ this.id = _id; }
public int getId(){ return this.id; }
}
And I am trying to process following JSON:
{
"id": "blah"
}
Obviously, there is a problem here ("blah" cannot be parsed to an int)
Formerly, Jackson throws something like org.codehaus.jackson.map.JsonMappingException: Can not construct instance of java.lang.Integer from String value 'blah': not a valid Integer value
I agree with this, but I'd like to register something somewhere allowing to ignore this type of mapping errors.
I tried with a DeserializationProblemHandler registered (see here) but it seems to only work on unknown properties and not deserialization problems.
Have you any clue on this issue?
I succeeded to solve my problem, thanks to Tatu from Jackson ML.
I had to use custom non blocking deserializers for every primitive types handled in Jackson.
Something like this factory :
public class JacksonNonBlockingObjectMapperFactory {
/**
* Deserializer that won't block if value parsing doesn't match with target type
* #param <T> Handled type
*/
private static class NonBlockingDeserializer<T> extends JsonDeserializer<T> {
private StdDeserializer<T> delegate;
public NonBlockingDeserializer(StdDeserializer<T> _delegate){
this.delegate = _delegate;
}
#Override
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
try {
return delegate.deserialize(jp, ctxt);
}catch (JsonMappingException e){
// If a JSON Mapping occurs, simply returning null instead of blocking things
return null;
}
}
}
private List<StdDeserializer> jsonDeserializers = new ArrayList<StdDeserializer>();
public ObjectMapper createObjectMapper(){
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule customJacksonModule = new SimpleModule("customJacksonModule", new Version(1, 0, 0, null));
for(StdDeserializer jsonDeserializer : jsonDeserializers){
// Wrapping given deserializers with NonBlockingDeserializer
customJacksonModule.addDeserializer(jsonDeserializer.getValueClass(), new NonBlockingDeserializer(jsonDeserializer));
}
objectMapper.registerModule(customJacksonModule);
return objectMapper;
}
public JacksonNonBlockingObjectMapperFactory setJsonDeserializers(List<StdDeserializer> _jsonDeserializers){
this.jsonDeserializers = _jsonDeserializers;
return this;
}
}
Then calling it like this way (pass as deserializers only those you want to be non blocking) :
JacksonNonBlockingObjectMapperFactory factory = new JacksonNonBlockingObjectMapperFactory();
factory.setJsonDeserializers(Arrays.asList(new StdDeserializer[]{
// StdDeserializer, here, comes from Jackson (org.codehaus.jackson.map.deser.StdDeserializer)
new StdDeserializer.ShortDeserializer(Short.class, null),
new StdDeserializer.IntegerDeserializer(Integer.class, null),
new StdDeserializer.CharacterDeserializer(Character.class, null),
new StdDeserializer.LongDeserializer(Long.class, null),
new StdDeserializer.FloatDeserializer(Float.class, null),
new StdDeserializer.DoubleDeserializer(Double.class, null),
new StdDeserializer.NumberDeserializer(),
new StdDeserializer.BigDecimalDeserializer(),
new StdDeserializer.BigIntegerDeserializer(),
new StdDeserializer.CalendarDeserializer()
}));
ObjectMapper om = factory.createObjectMapper();
You might want to let your controller handle the problem by adding a method that handles this specific exception
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseBody
public String handleHttpMessageNotReadableException(HttpMessageNotReadableException ex)
{
JsonMappingException jme = (JsonMappingException) ex.getCause();
return jme.getPath().get(0).getFieldName() + " invalid";
}
Of course, the line
JsonMappingException jme = (JsonMappingException) ex.getCause();
might throw a class cast exception for some cases but i haven't encountered them yet.
I have written a simple error handler which will give you some kind of error which you can return to user with bad request as status code. Use #JsonProperty required = true to get error related to missing properties. Jackson version 2.9.8.
public class JacksonExceptionHandler {
public String getErrorMessage(HttpMessageNotReadableException e) {
String message = null;
boolean handled = false;
Throwable cause = e.getRootCause();
if (cause instanceof UnrecognizedPropertyException) {
UnrecognizedPropertyException exception = (UnrecognizedPropertyException) cause;
message = handleUnrecognizedPropertyException(exception);
handled = true;
}
if (cause instanceof InvalidFormatException) {
InvalidFormatException exception = (InvalidFormatException) cause;
message = handleInvalidFormatException(exception);
handled = true;
}
if (cause instanceof MismatchedInputException) {
if (!handled) {
MismatchedInputException exception = (MismatchedInputException) cause;
message = handleMisMatchInputException(exception);
}
}
if (cause instanceof JsonParseException) {
message = "Malformed json";
}
return message;
}
private String handleInvalidFormatException(InvalidFormatException exception) {
String reference = null;
if (!exception.getPath().isEmpty()) {
String path = extractPropertyReference(exception.getPath());
reference = removeLastCharacter(path);
}
Object value = exception.getValue();
return "Invalid value '" + value + "' for property : " + reference;
}
private String handleUnrecognizedPropertyException(UnrecognizedPropertyException exception) {
String reference = null;
if (!exception.getPath().isEmpty()) {
String path = extractPropertyReference(exception.getPath());
reference = removeLastCharacter(path);
}
return "Unknown property : '" + reference + "'";
}
private String handleMisMatchInputException(MismatchedInputException exception) {
String reference = null;
if (!exception.getPath().isEmpty()) {
reference = extractPropertyReference(exception.getPath());
}
String property = StringUtils.substringBetween(exception.getLocalizedMessage(), "'", "'");
// if property missing inside nested object
if (reference != null && property!=null) {
return "Missing property : '" + reference + property + "'";
}
// if invalid value given to array
if(property==null){
return "Invalid values at : '"+ reference +"'";
}
// if property missing at root level
else return "Missing property : '" + property + "'";
}
// extract nested object name for which property is missing
private String extractPropertyReference(List<JsonMappingException.Reference> path) {
StringBuilder stringBuilder = new StringBuilder();
path.forEach(reference -> {
if(reference.getFieldName() != null) {
stringBuilder.append(reference.getFieldName()).append(".");
// if field is null means it is array
} else stringBuilder.append("[].");
}
);
return stringBuilder.toString();
}
// remove '.' at the end of property path reference
private String removeLastCharacter(String string) {
return string.substring(0, string.length() - 1);
}
}
and call this class object in global advice like this
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String message = new JacksonExceptionHandler().generator.getErrorMessage(ex);
if(message == null){
return ResponseEntity.badRequest().body("Malformed json");
}
return ResponseEntity.badRequest().body(message);
}
Create a simple Mapper:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JSONProcessingErroMapper implements ExceptionMapper<InvalidFormatException> {
#Override
public Response toResponse(InvalidFormatException ex) {
return Response.status(400)
.entity(new ClientError("[User friendly message]"))
.type(MediaType.APPLICATION_JSON)
.build();
}
}
DeserializationProblemHandler now has a lot more methods, such as handleUnexpectedToken and handleWeird*Value. It should be able to handle anything one needs.
Subclass it, override methods you need, then add it to your ObjectMapper with addHandler(DeserializationProblemHandler h).

Categories