Java, Jackson, Custom deserializer with extended classes - java

Using Jackson 2.10., I am trying to write a custom deserializer for a base class, but I have to deserialize fields with unknown field name. Then there are extended class that can also extend this serializer.
I have tried to use the #AnyGetter, and #AnySetter to accomplish it, and it kind of does work. Now I am just wondering if there is a way to do it through a custom deserializer.
I could do it with a base class, but it fails when some class extends it.
Here is the sample of what I have done.
The following is just the base class and its serializer and how I used in in the main.
//BaseClass
#JsonDeserialize(using = BaseClassDeserializer.class)
public static class BaseClass {
private ObjectNode customFields = JsonNodeFactory.instance.objectNode();
private int baseInt;
public int getBaseInt() {
return baseInt;
}
public void setBaseInt(int baseInt) {
this.baseInt = baseInt;
}
public JsonNode getCustomFields() {
return customFields;
}
public void setCustomFields(ObjectNode customFields) {
this.customFields = customFields;
}
public void putCustomFields(String key, JsonNode node) {
this.customFields.set(key, node);
}
}
// BaseClassDeserializer
public static class BaseClassDeserializer extends StdDeserializer<BaseClass> {
public BaseClassDeserializer() {
this(null);
}
public BaseClassDeserializer(Class<?> vc) {
super(vc);
}
#Override
public BaseClass deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JsonProcessingException {
BaseClass result = new BaseClass();
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
result.setBaseInt((Integer) ((IntNode) node.get("baseInt")).numberValue());
node.fieldNames();
Iterator<String> iterator = node.fieldNames();
while (iterator.hasNext()) {
String fieldName = iterator.next();
if (!"baseInt".equals(fieldName)) {
result.putCustomFields(fieldName, node.get(fieldName));
}
}
return result;
}
}
// main
public static void main(String[] args) throws JsonProcessingException {
String json = "{\n"
+ "\t\"baseInt\": 1,\n"
+ "\t\"customObject\" : {\n"
+ "\t\t\"key\": \"value\"\n"
+ "\t},\n"
+ "\t\"customString\" : \"STRING\",\n"
+ "\t\"extendedString\" : \"STRING\"\n"
+ "}";
ObjectMapper mapper = new ObjectMapper();
BaseClass myClass = mapper.readValue(json, BaseClass.class);
}
By going looking through the debugger, the fields are successfully loaded.
Now I am trying to extend BaseClass
// ExtendedClass
public static class ExtendedClass extends BaseClass {
#JsonProperty("extendedString")
private String extendedString;
public String getExtendedString() {
return extendedString;
}
public void setExtendedString(String extendedString) {
this.extendedString = extendedString;
}
}
public static void main(String[] args) throws JsonProcessingException {
String json = "{\n"
+ "\t\"baseInt\": 1,\n"
+ "\t\"customObject\" : {\n"
+ "\t\t\"key\": \"value\"\n"
+ "\t},\n"
+ "\t\"customString\" : \"STRING\",\n"
+ "\t\"extendedString\" : \"STRING\"\n"
+ "}";
ObjectMapper mapper = new ObjectMapper();
ExtendedClass myClass = mapper.readValue(json, ExtendedClass.class);
}
And this crashes with a
BaseClass cannot be cast to ExtendedClass exception.
I am guessing I have to pass along the deserialization to the child class' deserializer, but I cannot figure out how.

In your deserialiser you always return object of type BaseClass and it can not be cast to ExtendedClass. You need to implement type recognition feature in your deserialiser. In your case, returned type depends from properties JSON payload contains. If JSON payload contains extendedString property you know you need to return ExtendedClass in other case just return BaseClass. Your deserialiser could look like below:
class BaseClassDeserializer extends StdDeserializer<BaseClass> {
public BaseClassDeserializer() {
super(BaseClass.class);
}
#Override
public BaseClass deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ObjectNode root = jsonParser.readValueAsTree();
List<String> names = getNames(root);
BaseClass result = findAndInitCustomType(names, root);
result = initBase(names, result, root);
initCustomFields(names, root, result);
return result;
}
private void initCustomFields(List<String> names, ObjectNode root, BaseClass result) {
for (String name : names) {
result.putCustomFields(name, root.get(name));
}
}
private BaseClass findAndInitCustomType(List<String> names, ObjectNode root) {
final String extendedString = "extendedString";
if (names.contains(extendedString)) {
ExtendedClass result = new ExtendedClass();
result.setExtendedString(root.get(extendedString).asText());
names.remove(extendedString);
return result;
}
// else - check other custom fields for another types.
// if not available return null
return null;
}
private BaseClass initBase(List<String> names, BaseClass baseClass, ObjectNode root) {
if (baseClass == null) {
baseClass = new BaseClass();
}
final String baseInt = "baseInt";
if (names.contains(baseInt)) {
baseClass.setBaseInt(root.get(baseInt).asInt());
names.remove(baseInt);
}
return baseClass;
}
private List<String> getNames(ObjectNode root) {
List<String> names = new ArrayList<>();
root.fieldNames().forEachRemaining(names::add);
return names;
}
}
Example usage:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws Exception {
String baseJson = "{"
+ "\"baseInt\": 1,\n"
+ "\t\"customObject\" : {\n"
+ "\t\t\"key\": \"value\"\n"
+ "\t},\n"
+ "\t\"customString\" : \"STRING\""
+ "}";
String extendedJson = "{"
+ "\t\"baseInt\": 1,\n"
+ "\t\"customObject\" : {\n"
+ "\t\t\"key\": \"value\"\n"
+ "\t},\n"
+ "\t\"customString\" : \"STRING\",\n"
+ "\t\"extendedString\" : \"STRING\"\n"
+ "}";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(baseJson, BaseClass.class));
System.out.println(mapper.readValue(extendedJson, BaseClass.class));
System.out.println(mapper.readValue(extendedJson, ExtendedClass.class));
}
}
Above code prints:
BaseClass{customFields={"customObject":{"key":"value"},"customString":"STRING"}, baseInt=1}
ExtendedClass{extendedString='STRING'} BaseClass{customFields={"customObject":{"key":"value"},"customString":"STRING"}, baseInt=1}
ExtendedClass{extendedString='STRING'} BaseClass{customFields={"customObject":{"key":"value"},"customString":"STRING"}, baseInt=1}
Improvements:
In BaseClass class instead of ObjectNode use Map<String, JsonNode> or even Map<String, Object>. It is not a good idea to tie POJO classes with 3-rd party libraries.
You do not need to use #JsonProperty annotation if you manually handle deserialisation.

Related

Deserialize string using custom deserializer specified in field of class

I need to write a method that takes some object, some field name fieldName that exists in the given object's class, and some field value value. The value is the JSON-serialized form of the field. That method shall take the value and deserialize it accordingly, something like this:
static void setField(Object obj, String fieldName, String value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName)
Object valObj = objectMapper.readValue(value, field.getType());
field.set(obj, valObj);
}
(I actually only need to retrieve the deserialized value, and not set it again, but this makes it a better example.)
This works, as long as jackson's default deserialization is sufficient. Now let's assume I have a class with a custom (de)serializer:
class SomeDTO {
String foo;
#JsonSerialize(using = CustomInstantSerializer.class)
#JsonDeserialize(using = CustomInstantDeserializer.class)
Instant bar;
}
One possible solution would be to manually check for JsonDeserialize annotations. However, I really do not want to try to replicate whatever policies Jackson follows to decide what serializer to use, as that seems brittle (for example globally registered serializers).
Is there a good way to deserialize the value using the field's deserialization configuration defined in the DTO class? Maybe deserializing the value into the field's type while passing the field's annotations along to Jackson, so they get honored?
I managed to get a hold of an AnnotatedMember instance, which holds all the required information (JSON-annotations and reflective field- or setter/getter-access), but couldn't figure out how I would use it to deserialize a standalone value due to lack of documentation:
final JavaType dtoType = objectMapper.getTypeFactory().constructType(SomeDTO.class);
final BeanDescription description = objectMapper.getDeserializationConfig().introspect(dtoType);
for (BeanPropertyDefinition propDef: beanDescription.findProperties()) {
final AnnotatedMember mutator = propertyDefinition.getNonConstructorMutator();
// now what? Also: How do I filter for the correct property?
}
One possibility would be to serialize the object, replace the given field, and then deserialize it again. This can be easily done when serializing from/to JsonNode instead of JSON-String, like this:
static Object setField(Object obj, String fieldName, String value) throws Exception {
// note: produces a new object instead of modifying the existing one
JsonNode node = objectMapper.valueToTree(obj);
((ObjectNode) node).put(fieldName, value);
return objectMapper.readValue(node.traverse(), obj.getClass());
}
However, serializing and deserializing a whole object just to deserialize a single field seems like a lot of overhead, and might be brittle because other aspects of the DTO class affect the deserialization process of the single field
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.util.Map;
public final class Jackson {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
public static void main(String[] args) throws IOException {
Dto source = makeDto("Master", 31337);
Dto dst = makeDto("Slave", 0xDEADBEEF);
//1. read value of field "fieldName" from json source
//2. clones destination object, sets up field "fieldName" and returns it
//3. in case of no field either on "src" or "dst" - throws an exception
Object result = restoreValue(dst, "details", OBJECT_MAPPER.writeValueAsString(source));
System.out.println(result);
}
private static Object restoreValue(Object targetObject, String fieldName, String sourceObjectAsJson) throws IOException {
String targetObjectAsJson = OBJECT_MAPPER.writeValueAsString(targetObject);
Map sourceAsMap = OBJECT_MAPPER.readValue(sourceObjectAsJson, Map.class);
Map targetAsMap = OBJECT_MAPPER.readValue(targetObjectAsJson, Map.class);
targetAsMap.put(fieldName, sourceAsMap.get(fieldName));
String updatedTargetAsJson = OBJECT_MAPPER.writeValueAsString(targetAsMap);
return OBJECT_MAPPER.readValue(updatedTargetAsJson, targetObject.getClass());
}
private static Dto makeDto(String name, int magic) {
Dto dto = new Dto();
dto.setName(name);
CustomDetails details = new CustomDetails();
details.setMagic(magic);
dto.setDetails(details);
return dto;
}
private static final class Dto {
private String name;
#JsonSerialize(using = CustomDetails.CustomDetailsSerializer.class)
#JsonDeserialize(using = CustomDetails.CustomDetailsDeserializer.class)
private CustomDetails details;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public CustomDetails getDetails() {
return details;
}
public void setDetails(CustomDetails details) {
this.details = details;
}
#Override
public String toString() {
return "Dto{" +
"name='" + name + '\'' +
", details=" + details +
'}';
}
}
private static final class CustomDetails {
private int magic;
public int getMagic() {
return magic;
}
public void setMagic(int magic) {
this.magic = magic;
}
#Override
public String toString() {
return "CustomDetails{" +
"magic=" + magic +
'}';
}
public static final class CustomDetailsSerializer extends StdSerializer<CustomDetails> {
public CustomDetailsSerializer() {
this(null);
}
public CustomDetailsSerializer(Class<CustomDetails> t) {
super(t);
}
#Override
public void serialize(CustomDetails details, JsonGenerator jg, SerializerProvider serializerProvider) throws IOException {
jg.writeStartObject();
jg.writeNumberField("_custom_property_magic", details.magic);
jg.writeEndObject();
}
}
private static final class CustomDetailsDeserializer extends StdDeserializer<CustomDetails> {
public CustomDetailsDeserializer() {
this(null);
}
public CustomDetailsDeserializer(Class<CustomDetails> t) {
super(t);
}
#Override
public CustomDetails deserialize(JsonParser jp, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int magic = (Integer) node.get("_custom_property_magic").numberValue();
CustomDetails
customDetails = new CustomDetails();
customDetails.setMagic(magic);
return customDetails;
}
}
}
}
so the output is:
Dto{name='Slave', details=CustomDetails{magic=31337}}

How do I parse Json data with Gson when not everything is labelled

I have happily been using Google Gson to parse extract some JSON metadata of the form
{
"lowlevel": {
"average_loudness": 0.570070445538
},
"rhythm": {
"beats_count": 502,
"bpm": 128.347702026
},
"tonal": {
"chords_changes_rate": 0.0534749031067
"tuning_diatonic_strength": 0.431238204241,
"tuning_equal_tempered_deviation": 0.164615109563,
"tuning_frequency": 434.193115234,
"tuning_nontempered_energy_ratio": 0.847496032715
}
}
Using this
public class AcousticBrainzLowlevelWrapper
{
private AcousticBrainzLowLevelRhythm rhythm;
private AcousticBrainzLowLevelTonal tonal;
public AcousticBrainzLowLevelRhythm getRhythm()
{
return rhythm;
}
public void setRhythm(AcousticBrainzLowLevelRhythm rhythm)
{
this.rhythm = rhythm;
}
public AcousticBrainzLowLevelTonal getTonal()
{
return tonal;
}
public void setTonal(AcousticBrainzLowLevelTonal tonal)
{
this.tonal = tonal;
}
}
and
AcousticBrainzLowlevelWrapper low = gson.fromJson(result, AcousticBrainzLowlevelWrapper.class) ;
(Full JSON can be seen here)
but now the API has been extended to allow multiple lookups such as this url
which now returns
{
"96685213-a25c-4678-9a13-abd9ec81cf35": {
"0": {
"lowlevel": {
"average_loudness": 0.570070445538
},
"rhythm": {
"beats_count": 502,
"bpm": 128.347702026
},
"tonal": {
"chords_changes_rate": 0.0534749031067
"tuning_diatonic_strength": 0.431238204241,
"tuning_equal_tempered_deviation": 0.164615109563,
"tuning_frequency": 434.193115234,
"tuning_nontempered_energy_ratio": 0.847496032715
}
}
.....
"78787888-a25c-4678-9a13-abd9ec81cf35": {
"0": {
"lowlevel": {
......
..
The difference being that the json doesn't define what "96685213-a25c-4678-9a13-abd9ec81cf35" and "78787888-a25c-4678-9a13-abd9ec81cf35" are, or what "0" is.
So I know what they represent (MusicBrainzRecording and offset) but I cannot create a class like AcousticBrainzLowlevelWrapper to represent this, so how do I parse this new api.
Update
I tried creating
public class AcousticBrainzLowLevelList
{
private Map<String, AcousticBrainzLowlevelWrapper> data = new HashMap<>();
public Map<String, AcousticBrainzLowlevelWrapper> getData()
{
return data;
}
public void setData(Map<String, AcousticBrainzLowlevelWrapper> data)
{
this.data = data;
}
}
and then calling
AcousticBrainzLowLevelList lowMap = gson.fromJson(result, AcousticBrainzLowLevelList.class) ;
but nothing get added to the map. Unsuprisingly because data I dont' see how can i give a name since there is no consistent name at the top level.
It seems to me that your input JSON could be parsed to produce a Java class of type Map<String,Map<Integer,AcousticBrainzLowlevelWrapper>> :
Type type = new TypeToken<Map<String,Map<Integer,AcousticBrainzLowlevelWrapper>>>(){}.getType();
Map<String,Map<Integer,AcousticBrainzLowlevelWrapper>> result = gson.fromJson(json, type);
As I wrote it, I might as well post it:
Similar to Maurice's answer
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.Map;
public class Main {
private final static String jsonSingle =
"{ \"attribute1\": \"value1\", \"attribute2\": \"value2\" }";
private final static String jsonMultiple =
"{\n" +
" \"96685213-a25c-4678-9a13-abd9ec81cf35\": {\n" +
" \"0\": { \"attribute1\": \"value1\", \"attribute2\": \"value2\" }\n" +
" },\n" +
" \"78787888-a25c-4678-9a13-abd9ec81cf35\": {\n" +
" \"0\": { \"attribute1\": \"value3\", \"attribute2\": \"value4\" }\n" +
"}}";
public static void main(String[] args) {
MyBean bean = new Gson().fromJson(jsonSingle, MyBean.class);
System.out.println(bean);
Type type = new TypeToken<Map<String, Map<String, MyBean>>>(){}.getType();
Map<String, String> myMap = new Gson().fromJson(jsonMultiple, type);
System.out.println(myMap);
}
}
MyBean class:
class MyBean {
String attribute1;
String attribute2;
public String getAttribute1() {
return attribute1;
}
public void setAttribute1(String attribute1) {
this.attribute1 = attribute1;
}
public String getAttribute2() {
return attribute2;
}
public void setAttribute2(String attribute2) {
this.attribute2 = attribute2;
}
#Override
public String toString() {
return "MyBean: <attribute1: " + attribute1 + " | " + "attribute2: " + attribute2 + ">";
}
}
Outputs:
MyBean: <attribute1: value1 | attribute2: value2>
and
{96685213-a25c-4678-9a13-abd9ec81cf35={0=MyBean: <attribute1: value1 | attribute2: value2>}, 78787888-a25c-4678-9a13-abd9ec81cf35={0=MyBean: <attribute1: value3 | attribute2: value4>}}

JSON String to Java String

I have these JSON String:
{
"Results": {
"output1": {
"type": "table",
"value": {
"ColumnNames": ["userId", "documentId", "Scored Labels", "Scored Probabilities"],
"ColumnTypes": ["String", "String", "Boolean", "Double"],
"Values": [["100213199594809000000", "1Ktol-SWvAh8pnHG2O7HdPrfbEVZWX3Vf2YIPYXA_8gI", "False", "0.375048756599426"], ["103097844766994000000", "1jYsTPJH8gaIiATix9x34Ekcj31ifJMkPNb0RmxnuGxs", "True", "0.753859758377075"]]
}
}
}
}
And I want to have only the ColumnNames and the Values. I have tried it with something like this:
Map<String,Object> map = mapper.readValue(filename, Map.class);
String CN = (String) map.get("ColumnNames");
But then I get the following error:
Exception in thread "main" org.codehaus.jackson.JsonParseException: Unexpected character ('A' (code 65)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
at [Source: java.io.StringReader#64232b15; line: 1, column: 2]`
I've worked only few times with JSON. Can anybody help me here?
The best case for me would be something like this, which I've done in another case:
String uId = (String) attr.get("userId");
Is it possible?
So now I've done this:
I try it like this:
public class ClientPOJO {
private String userId;
private String documentId;
public String getuserId() {
return userId;
}
public void setuserId(String userId) {
this.userId = userId;
}
public String getdocumentId() {
return documentId;
}
public void setdocumentId(String documentId) {
this.documentId = documentId;
}
}
and then:
ObjectMapper mapper = new ObjectMapper();
ClientPOJO clientes= mapper.readValue(filename, ClientPOJO.class);
String uid = clientes.getuserId();
But now when I make a Prtinout I'll get the same error like before:
Exception in thread "main" org.codehaus.jackson.JsonParseException: Unexpected character ('A' (code 65)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
at [Source: java.io.StringReader#7a6eb29d; line: 1, column: 2]
Java- Convert JSON string into string / integer / Object
String jsonString = "{"username":"Gajender"}";
org.json.JSONObject jsonObj =new JSONObject(jsonString);
String name = (String) jsonObj.get("username").toString();
Below is an example to illustrate a generic approach to solve your problem ( based on Jackson library). You may like to enhance the solution to meet your all requirements.
Comments inlined.
package com.stackoverflow;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
// Junit class
public class TableDeserExample {
// sample input
String inputJson = "{\n" +
" \"Results\": {\n" +
" \"output1\": {\n" +
" \"type\": \"table\",\n" +
" \"value\": {\n" +
" \"ColumnNames\": [\"userId\", \"documentId\", \"Scored Labels\", \"Scored Probabilities\"],\n" +
" \"ColumnTypes\": [\"String\", \"String\", \"Boolean\", \"Double\"],\n" +
" \"Values\": [[\"100213199594809000000\", \"1Ktol-SWvAh8pnHG2O7HdPrfbEVZWX3Vf2YIPYXA_8gI\", \"False\", \"0.375048756599426\"], [\"103097844766994000000\", \"1jYsTPJH8gaIiATix9x34Ekcj31ifJMkPNb0RmxnuGxs\", \"True\", \"0.753859758377075\"]]\n"
+
" }\n" +
" }\n" +
" }\n" +
"}";
// POJO to map the Json structure. You may want to make it generalize based
// on field "type"
// (https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization)
public static class Result {
private String type;
private TableResult value;
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
public void setValue(TableResult value) {
this.value = value;
}
public TableResult getValue() {
return this.value;
}
}
// Pojo for table result
public static class TableResult {
private List<String> columnNames;
private List<String> columnTypes;
private List<Object[]> values;
#JsonProperty("ColumnNames")
public List<String> getColumnNames() {
return this.columnNames;
}
public void setColumnNames(List<String> columnNames) {
this.columnNames = columnNames;
}
#JsonProperty("ColumnTypes")
public List<String> getColumnTypes() {
return this.columnTypes;
}
public void setColumnTypes(List<String> columnTypes) {
this.columnTypes = columnTypes;
}
#JsonProperty("Values")
public List<Object[]> getValues() {
return this.values;
}
public void setValues(List<Object[]> values) {
this.values = values;
}
}
// Top level Json POJO
public static class ResultContainer {
private Map<String, Result> results;
#JsonProperty("Results")
public Map<String, Result> getResults() {
return this.results;
}
public void setResults(Map<String, Result> results) {
this.results = results;
}
}
// A contract to map the result "values" to the expected object
public static interface ResultMapper<T> {
T map(TableResult map, Object[] row);
}
// Basic implementation for mapping user object from json "values[i]" array
public static class UserTableResultMapper implements ResultMapper<User> {
#Override
public User map(TableResult result, Object[] row) {
User user = new User();
// Here use any mapper logic based on column name
// Retrieved from result object.
// Below are for illustration only
user.setId(String.valueOf(row[0]));
user.setDocumentId(String.valueOf(row[1]));
return user;
}
}
// A result reader class
public static class ResultReader<T> implements Iterable<T> {
private TableResult result;
private ResultMapper<T> mapper;
public ResultReader(TableResult result, ResultMapper<T> mapper) {
this.result = result;
this.mapper = mapper;
}
#Override
public Iterator<T> iterator() {
final Iterator<Object[]> itr = result.getValues().iterator();
return new Iterator<T>() {
#Override
public void remove() {
throw new UnsupportedOperationException();
}
#Override
public T next() {
Object[] values = itr.next();
return mapper.map(result, values);
}
#Override
public boolean hasNext() {
return itr.hasNext();
}
};
};
}
public static class User {
private String id;
private String documentId;
// and others
public String getId() {
return this.id;
}
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
public void setId(String id) {
this.id = id;
}
public String getDocumentId() {
return this.documentId;
}
}
#Test
public void simpleTest() throws Exception {
ObjectMapper mapper = new ObjectMapper();
ResultContainer file = mapper.readValue(inputJson, ResultContainer.class);
Result result = file.getResults().get("output1");
ResultReader<User> userResultReader = new ResultReader<>(result.getValue(), new UserTableResultMapper());
for (User user : userResultReader) {
System.out.println(user.getId() + " : " + user.getDocumentId());
}
}
}
If you know exactly the structure of your json (like the json you have post) then you can using Gson to get your object like this:
JsonParser parser = new JsonParser();
JsonObject json = (JsonObject) parser.parse("your_json_string_here");
String column = json.get("Results").getAsJsonObject().get("output1").getAsJsonObject().get("value").getAsJsonObject().get("ColumnNames").getAsJsonArray().toString();
String value = json.get("Results").getAsJsonObject().get("output1").getAsJsonObject().get("value").getAsJsonObject().get("Values").getAsJsonArray().toString();
System.out.println(column);
System.out.println(value);
If you need some things more generic then you can parse your json string to a HashMap<String, Object> then using recursion to read the HashMap and get the value you want.
Example (in my code, the type of Map will corresponding to a Json Object, type of List will corresponding to the Array in Json string):
Type type = new TypeToken<HashMap<String, Object>>() {}.getType();
Gson gson = new Gson();
HashMap<String, Object> map = gson.fromJson("your_json_string_here", type);
for (String key : map.keySet()) {
Object obj = map.get(key);
if (obj instanceof List) {
for (Object o : (List) obj) {
if (o instanceof Map) {
loop((Map) o);
} else {
System.out.println(key + " : " + o);
}
}
} else if (obj instanceof Map) {
loop((Map) obj);
} else {
System.out.println(key + " : " + obj);
}
}
}
private static void loop(Map<String, Object> map) {
for (String key : map.keySet()) {
Object obj = map.get(key);
if (obj instanceof List) {
for (Object o : (List) obj) {
if (o instanceof Map) {
loop((Map) o);
} else {
System.out.println(key + " : " + o);
}
}
} else if (obj instanceof Map) {
loop((Map) obj);
} else {
System.out.println(key + " : " + obj);
}
}
}
Neither Jackson nor any other library will parse the Values array into objects with client data like your POJO. You can achieve this by getting the raw tree of data in this JSON and constructing objects by iterating over the Values array inside this tree. Assuming the order of ColumnNames is fixed then you can parse with Jackson like this:
final ObjectMapper mapper = new ObjectMapper();
final JsonNode tree = mapper.readTree(json);
final JsonNode values = tree.findValue("Values");
final List<ClientPOJO> clients = new ArrayList<>();
for (JsonNode node : values) {
final ClientPOJO client = new ClientPOJO();
client.setUserId(node.get(0).asText());
client.setDocumentId(node.get(1).asText());
client.setScoredLabels(node.get(2).asBoolean());
client.setScoredProbabilities(node.get(3).asDouble());
clients.add(client);
}
Docs for JsonNode. Basically with findValue you can get another node deep into the tree, with get you can get array elements by index and with asText etc you parse a value in JSON into the appropriate type in Java.
Since you seem to be flexible in choice of JSON parsing library I would suggest Jackson 2 from com.fasterxml instead of Jackson 1 from org.codehaus that you tried.

Deserialize JSON to Java using Jackson 2

I have a JSON:
{"evaluationPart": {
"generatedId": "48D5181DB8704F8AB5FC998964AD9075",
"evaluationQuestionPartOption": {
"generatedId": "48D5181DB8704F8AB5FC998964AD9075"
}
}}
I've created java classes for it to represent it:
The root class:
#JsonIgnoreProperties(value = {"evaluationPart"})
public class JsonEvaluationPart {
#JsonProperty("generatedId")
private String generatedId;
#JsonProperty("evaluationQuestionPartOption")
private JsonQuestionOption questionOption;
public String getGeneratedId() {
return generatedId;
}
public void setGeneratedId(String generatedId) {
this.generatedId = generatedId;
}
public JsonQuestionOption getQuestionOption() {
return questionOption;
}
public void setQuestionOption(JsonQuestionOption questionOption) {
this.questionOption = questionOption;
}
}
And the JsonQuestionOption class:
public class JsonQuestionOption {
#JsonProperty("generatedId")
private String generatedId;
public String getGeneratedId() {
return generatedId;
}
public void setGeneratedId(String generatedId) {
this.generatedId = generatedId;
}
}
I have written a small JUnit test to check how it goes:
public class JsonReaderTest {
/**
* Logger for this class.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(JsonReaderTest.class);
private ObjectMapper objectMapper;
private static final String JSON = "{\"evaluationPart\": {\n" +
" \"generatedId\": \"48D5181DB8704F8AB5FC998964AD9075\",\n" +
" \"evaluationQuestionPartOption\": {\n" +
" \"generatedId\": \"48D5181DB8704F8AB5FC998964AD9075\"\n" +
" }\n" +
"}}";
#Before
public void setUp()
throws Exception {
LOGGER.debug("Creating the object mapper.");
objectMapper = new ObjectMapper();
LOGGER.debug("Object mapper successfully created. {}", objectMapper);
}
#Test
public void testJsonReader()
throws Exception {
JsonEvaluationPart partType = objectMapper.readValue(JSON, JsonEvaluationPart.class);
assertNotNull(partType);
LOGGER.debug("Part: {}.", partType);
assertEquals(partType.getGeneratedId(), "48D5181DB8704F8AB5FC998964AD9075");
assertEquals(partType.getQuestionOption().getGeneratedId(), "48D5181DB8704F8AB5FC998964AD9075");
}
}
The problem is that when I am reading my JSON like this:
JsonEvaluationPart partType = objectMapper.readValue(JSON, JsonEvaluationPart.class);
All the properties in partType are null. What I am doing wrong here and how to solve this?
According to the documentation JsonIgnoreProperties means:
Annotation that can be used to either suppress serialization of properties
(during serialization), or ignore processing of JSON properties read (during
deserialization).
Just try replacing:
#JsonIgnoreProperties(value = {"evaluationPart"})
with:
#JsonTypeName("evaluationPart")
Your Json Headers is wrong.
use
#JsonTypeName("evaluationPart")
instead of
#JsonIgnoreProperties(value = {"evaluationPart"})

Strict JSON parsing with Google's Gson?

Suppose I am using Google's Gson library to parse JSON into Java data structures.
Is there an easy way to throw an exception if there is a Java field that has no corresponding JSON? That is, I wish to require the JSON to have all the fields in the Java structure.
Gson doesn't have a JSON schema validation feature to specify that a particular element must be present, and it doesn't have a way to specify that a Java member must be populated. It might be nice to have such a feature available, such as with an #Required annotation. Head on over to the Gson Issues List and put in an enhancement request.
With Gson, you could enforce that specified JSON elements are present with a custom deserializer.
// output:
// [MyObject: element1=value1, element2=value2, element3=value3]
// [MyObject: element1=value1, element2=value2, element3=null]
// Exception in thread "main" com.google.gson.JsonParseException: Required Field Not Found: element2
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
public class Foo
{
static String jsonInput1 = "{\"element1\":\"value1\",\"element2\":\"value2\",\"element3\":\"value3\"}";
static String jsonInput2 = "{\"element1\":\"value1\",\"element2\":\"value2\"}";
static String jsonInput3 = "{\"element1\":\"value1\",\"element3\":\"value3\"}";
public static void main(String[] args)
{
GsonBuilder gsonBuilder = new GsonBuilder();
MyDeserializer deserializer = new MyDeserializer();
deserializer.registerRequiredField("element2");
gsonBuilder.registerTypeAdapter(MyObject.class, deserializer);
Gson gson = gsonBuilder.create();
MyObject object1 = gson.fromJson(jsonInput1, MyObject.class);
System.out.println(object1);
MyObject object2 = gson.fromJson(jsonInput2, MyObject.class);
System.out.println(object2);
MyObject object3 = gson.fromJson(jsonInput3, MyObject.class);
System.out.println(object3);
}
}
class MyObject
{
String element1;
String element2;
String element3;
#Override
public String toString()
{
return String.format(
"[MyObject: element1=%s, element2=%s, element3=%s]",
element1, element2, element3);
}
}
class MyDeserializer implements JsonDeserializer<MyObject>
{
List<String> requiredFields = new ArrayList<String>();
void registerRequiredField(String fieldName)
{
requiredFields.add(fieldName);
}
#Override
public MyObject deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
JsonObject jsonObject = (JsonObject) json;
for (String fieldName : requiredFields)
{
if (jsonObject.get(fieldName) == null)
{
throw new JsonParseException("Required Field Not Found: " + fieldName);
}
}
return new Gson().fromJson(json, MyObject.class);
}
}
A preferable approach might be to use an API that provides JSON Schema validation. Jackson has at least a rudimentary implementation available. JSON Tools looks to have a more mature one.
Here's an example with Jackson.
// output:
// Validating jsonInput1...
// Validating jsonInput2...
// Validating jsonInput3...
// $.element2: is missing and it is not optional
// [MyObject: element1=value1, element2=value2, element3=value3]
// [MyObject: element1=value1, element2=value2, element3=null]
// [MyObject: element1=value1, element2=null, element3=value3]
import java.util.List;
import org.codehaus.jackson.map.ObjectMapper;
import eu.vahlas.json.schema.JSONSchema;
import eu.vahlas.json.schema.JSONSchemaProvider;
import eu.vahlas.json.schema.impl.JacksonSchemaProvider;
public class Foo
{
static String jsonSchema =
"{" +
"\"description\":\"Serialized MyObject Specification\"," +
"\"type\":[\"object\"]," +
"\"properties\":" +
"{" +
"\"element1\":{\"type\":\"string\"}," +
"\"element2\":{\"type\":\"string\",\"optional\":false}," +
"\"element3\":{\"type\":\"string\",\"optional\":true}" +
"}" +
"}";;
static String jsonInput1 = "{\"element1\":\"value1\",\"element2\":\"value2\",\"element3\":\"value3\"}";
static String jsonInput2 = "{\"element1\":\"value1\",\"element2\":\"value2\"}";
static String jsonInput3 = "{\"element1\":\"value1\",\"element3\":\"value3\"}";
public static void main(String[] args) throws Exception
{
ObjectMapper mapper = new ObjectMapper();
JSONSchemaProvider schemaProvider = new JacksonSchemaProvider(mapper);
JSONSchema schema = schemaProvider.getSchema(jsonSchema);
System.out.println("Validating jsonInput1...");
validateAndLogErrors(jsonInput1, schema);
System.out.println("Validating jsonInput2...");
validateAndLogErrors(jsonInput2, schema);
System.out.println("Validating jsonInput3...");
validateAndLogErrors(jsonInput3, schema);
MyObject object1 = mapper.readValue(jsonInput1, MyObject.class);
System.out.println(object1);
MyObject object2 = mapper.readValue(jsonInput2, MyObject.class);
System.out.println(object2);
MyObject object3 = mapper.readValue(jsonInput3, MyObject.class);
System.out.println(object3);
}
static void validateAndLogErrors(String jsonInput, JSONSchema schema)
{
List<String> errors = schema.validate(jsonInput);
for (String error : errors)
{
System.out.println(error);
}
}
}
class MyObject
{
String element1;
String element2;
String element3;
void setElement1(String element1)
{
this.element1 = element1;
}
void setElement2(String element2)
{
this.element2 = element2;
}
void setElement3(String element3)
{
this.element3 = element3;
}
#Override
public String toString()
{
return String.format(
"[MyObject: element1=%s, element2=%s, element3=%s]",
element1, element2, element3);
}
}
You can recursively verify whether the json contains fields that are not declared in the class :
private static List<String> verifyElement(JsonObject element, Class klass) throws NoSuchFieldException, IllegalAccessException {
List<String> unknownFields = new ArrayList<>();
Set<String> classFields = new HashSet<>();
for (Field field : klass.getDeclaredFields()) {
if (!Modifier.isPublic(field.getModifiers())) {
throw new IllegalArgumentException("All fields must be public. Please correct this field :" + field);
}
}
for (Field field : klass.getFields()) {
classFields.add(field.getName());
}
// Verify recursively that the class contains every
for (Map.Entry<String, JsonElement> entry : element.entrySet()) {
if (!classFields.contains(entry.getKey())) {
unknownFields.add(klass.getCanonicalName() + "::" + entry.getKey() + "\n");
} else {
Field field = klass.getField(entry.getKey());
Class fieldClass = field.getType();
if (!fieldClass.isPrimitive() && entry.getValue().isJsonObject()) {
List<String> elementErrors = verifyElement(entry.getValue().getAsJsonObject(), fieldClass);
unknownFields.addAll(elementErrors);
}
}
}
return unknownFields;
}

Categories

Resources