I have a Java EE project that is using GSON library (Google's library for processing of JSON objects).
In my entity classes I use #Expose annotation to control which fields are considered by GSON. I also use serialize/deserialize properties on that annotation to control which fields are considered when serializing a Java object to JSON and which fields are considered when deserializing JSON objects to Java objects. For example:
public class Movie {
#Expose(serialize=true, deserialize=false)
#Id
#GeneratedValue
private long id;
#Expose(serialize=true, deserialize=true)
private String name;
#Expose(serialize=true, deserialize=true)
private String genre;
#Expose(serialize=false, deserialize=true)
private String secretID;
}
Here when I send the JSON object to be deserialized into Java object I send an object like this:
{
"name": "Memento",
"genre": "thriller",
"secretID": "123asd"
}
And, when I serialize Java object to JSON I get something like this:
{
"id": 1,
"name": "Memento",
"genre": "thriller"
}
I have this Java code:
public static void main(String[] args) {
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create();
String json = gson.toJson(new Movie());
System.out.println(json);
}
that generates this as it's output:
{
"id": 0,
"name": "",
"genre": ""
}
Those are fields that are marked to be serialized. However, what if I need to print out all of the fields that are marked to be deserialized, so that I can easier create a JSON object that will be used as input when creating new Movies.
The desired output is this:
{
"name": "",
"genre": "",
"secretID": ""
}
Note: I don't want to change serialize/deserialize properties on #Expose annotations because they are set to how my application needs to work. I just need an easy way to generate a template JSON objects that will be used as input to my application, so I don't have to type it manually.
You could implement more generic ExclusionStrategy like:
#RequiredArgsConstructor
public class IncludeListedFields implements ExclusionStrategy {
#NonNull
private Set<String> fieldsToInclude;
#Override
public boolean shouldSkipField(FieldAttributes f) {
return ! fieldsToInclude.contains(f.getName());
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
then use it like:
Set<String> fieldsToInclude =
new HashSet<>(Arrays.asList("name", "genre", "secretID"));
ExclusionStrategy es = new IncludeListedFields(fieldsToInclude);
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls()
.addSerializationExclusionStrategy(es).create();
Note following things:
You should not now use the builder method .excludeFieldsWithoutExposeAnnotation.
By default Gson does not serialize fileds with null values so you need to use builder method .serializeNulls(). This does not generate Json with string values "" but just null.
In your example Json fields contained empty strings as values but you did not introduce default constructor Movie() that would initialize field values to empty strings so they remain null. But if you initialize them - say to empty string ""- then they are not null & you do not need to use builder method .serializeNulls().
BUT if you really need and want only to serialize based on #Expose(deserialize=true) then the ExclusionStrategy can be just:
public class PrintDeserializeTrue implements ExclusionStrategy {
#Override
public boolean shouldSkipField(FieldAttributes f) {
Expose annotationExpose = f.getAnnotation(Expose.class);
if(null != annotationExpose) {
if(annotationExpose.deserialize())
return false;
}
return true;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
Related
Am trying to deserialize a complex JSON structure using GSON. The API provider complicates things by providing an array in the results with a random name.
This is the (simplified/generified) JSON:
{
"field_1": "value",
"field_2": "value",
"field_3": {
"RANDOM_NAME": [
{
"array_field_1": "value",
"array_field_2": "value",
"array_field_3": "value"
},
{
"array_field_1": "value",
"array_field_2": "value",
"array_field_3": "value"
}
]
},
"field_4": "value"
}
and this is the corresponding (highly simplified) POJO:
public class responseObject {
String field_1;
String field_2;
Field3 field_3;
String field_4;
class Field3{
ArrayObject[] arrayObjects;
}
class ArrayObject{
String array_field_1;
String array_field_2;
String array_field_3;
}
}
However, when i run responseObject response = new Gson().fromJson(getJSON(),responseObject.class); i get the following call stack:
indicating that field_3 was not properly deserialized and does not contain an array of ArrayObject.
In this post the answers reference how to convert the data to a map, but in my case the data structure of each item in the array is actually much larger than this simplified example, and it defeats the purpose of using GSON if i have to manually pick the data i need out of a complex list of nested maps. also having trouble getting these answers to work in my scenario where the random object is an array an not a plain json object.
how do i get the randomly named array in the JSON to properly deserialize into the variable responseObject.Field3.arrayObjects??
You can avoid the complexity of using a TypeAdapeter by making the type of field_3 Map<String, List<ArrayObject>>
public class responseObject {
String field_1;
String field_2;
Map<String, List<ArrayObject>> field_3;
String field_4;
class ArrayObject{
String array_field_1;
String array_field_2;
String array_field_3;
}
}
And then to get the first item out of the Map without knowing its key you can use:
public List<ResponseObject.ArrayObject> getFirstValue(Map<String, List<ResponseObject.ArrayObject>> field_3) {
return field_3.values().iterator().next();
}
This can be solved by writing a custom TypeAdapter for Field3 which ignores the name of the property and only reads the value. The TypeAdapter has to be created by a TypeAdapterFactory to allow getting the delegate adapter for ArrayObject[]:
class Field3TypeAdapterFactory implements TypeAdapterFactory {
public Field3TypeAdapterFactory() {
}
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
// Only support Field3 class
if (type.getRawType() != Field3.class) {
return null;
}
TypeAdapter<ArrayObject[]> fieldValueAdapter = gson.getAdapter(ArrayObject[].class);
// Cast is safe, check at beginning made sure type is Field3
#SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<Field3>() {
#Override
public void write(JsonWriter out, Field3 value) throws IOException {
throw new UnsupportedOperationException("Serialization is not supported");
}
#Override
public Field3 read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
in.beginObject();
// Skip the random property name
in.skipValue();
ArrayObject[] fieldValue = fieldValueAdapter.read(in);
in.endObject();
Field3 object = new Field3();
object.arrayObjects = fieldValue;
return object;
}
};
return adapter;
}
}
You can then either register the factory with a GsonBuilder, or you can annotate your Field3 class with #JsonAdapter. When using #JsonAdapter the factory class should have a no-args constructor.
I am trying to convert following JSON to Java object and ending up with UnrecognizedPropertyException.
{
"5214": [{
"name": "sdsds",
"age": "25",
"address": null
},
{
"name": "sdfds",
"age": "26",
"address": null
}]
}
Here "5214" is the random key that I get. I can covert it by modifying JSON little bit. But I want to know whether any possible way to convert the mentioned JSON. I even tried with following snippet taking some reference.
public class SampleTest {
private Map<String, List<EmployeeDetails>> employeeDetails = new HashMap<String, List<EmployeeDetails>>();
public Map<String, List<EmployeeDetails>> getEmployeeDetails() {
return employeeDetails;
}
public void setEmployeeDetails(Map<String, List<EmployeeDetails>> employeeDetails) {
this.employeeDetails = employeeDetails;
}
}
public class EmployeeDetails {
private String name;
private String age;
private String address;
//Getters and Setters
}
Can someone guide me on this?
Use Type Reference (Import Jackson Package for Java)
TypeReference<Map<String, List<EmployeeDetails>>> typeReference = new TypeReference<Map<String, List<EmployeeDetails>>>()
{
};
Map<String, List<EmployeeDetails>> employeeDetails = new ObjectMapper().readValue(jsonString, typeReference);
Check something from that
Maybe:
public class Data {
// String contain the Key, for example: 5214
Map<String, List<EmployeeDetails>> employeeDetails =
new HashMap<String,List<EmployeeDetails>>();
public Data() {
}
#JsonAnyGetter
public Map<String, List<EmployeeDetails>> getEmployeeDetails() {
return employeeDetails;
}
}
I would use custom deserializer with few helper classes. To make the code (matter of opinion I guess) clearer, create the list object:
#SuppressWarnings("serial")
#Getter #Setter
public class EmployeeDetailsList extends ArrayList<EmployeeDetails> {
// this will hold the arbitrary name of list. like 5214
private String name;
}
Then this list seems to be inside an object, say Wrapper:
#Getter
#RequiredArgsConstructor
#JsonDeserialize(using = WrapperDeserializer.class)
public class Wrapper {
private final EmployeeDetailsList employeeDetailsList;
}
So there is annotation #JsonDeserializer that handles deserializing Wrapper. It is not possible to directly deserialize unknown field names to some defined type so we need to use mechanism like this custom deserializer that inspects what is inside Wrapper and determines what to deserialize and how.
And here is how the deserializer works:
public class WrapperDeserializer extends JsonDeserializer<Wrapper> {
private final ObjectMapper om = new ObjectMapper();
#Override
public Wrapper deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
TreeNode node = p.readValueAsTree();
// This is the place for caution. You should somehow know what is the correct node
// Here I happily assume there is just the one and first
String fName = node.fieldNames().next();
EmployeeDetailsList edl = om.readValue(node.get(fName).toString(),
EmployeeDetailsList.class);
edl.setName(fName);
return new Wrapper(edl);
}
}
Please check it carefully it is not perfect in sense finding alwasy the correct node and maybe the instantiation can be done in other ways better. But it shoudl give you a hunch how it could be done.
I'm new with java and objectMapper. I'm trying to parse json field that is possible that a key have two types, it could be a string or array.
examples:
{
"addresses": [],
"full_name": [
"test name_1",
"test name_2"
],
}
or
{
{
"addresses": [],
"full_name": "test name_3",
}
}
Class example:
#JsonIgnoreProperties(ignoreUnknown = true)
#Data -> lombok.Data
public class Document {
private List<String> addresses;
#JsonProperty("full_name")
private String fullName;
}
I used objectMapper to deserialize json, works correctly when the 'full_name' field has a string but when arrive an array fail deserialization.
The idea is that when arrive a string put value in attribute but when arrive array, concatenate de array elements as string (String.join(",", value))
It's possible to apply custom deserialization in a class method? For example setFullName() (use lombok.Data)
I saw others examples in this site, but not work.
Thank's for all
From jackson 2.6 you can use JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY
#JsonProperty("full_name")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private String[] fullName;
Elaborating on #Deadpool answer, you can use setter which accept the array and then join it to string:
#JsonProperty("full_name")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
void setFullName(String[] name)
{
this.fullName = String.join(",", name);
}
Both answers are great. I just want to mention about custom Deserializer.
You can easily extend from StdDeserializer<Document> and override deserialize method:
public class DocumentDeserializer extends StdDeserializer<Document> {
#Override
public Document deserialize(JsonParser p, DeserializationContext ctxt, Document value) throws IOException {
JsonNode root = p.getCodec().readTree(p);
JsonNode node = root.get("full_name");
if(node.isArray()) {
//get array data from node iterator then join as String and
//call setFirstName
}
return value;
}
}
Then don't forget to call registerModule of ObjectMapper to register your deserializer
I have a Pojo that contains one member displayPropsJsonwhich is a clientside json string. It is validated with a JSON schema before storing on the server.
i.e.
public class Item {
Long id; //23
String name; //"itemsName"
String displayPropsJson; // "{\"bold\" : true, \"htmlAllowed\" : true, \"icon\" :\"star.jpg\" }"
}
I'd like the serialized version of this to output the displayPropsJson as displayProps sub object for example:
{
"id" :23,
"name: : "itemsName",
"displayProps" : {
"bold" : true,
"htmlAllowed" : true,
"icon" : "star.jpg"
}
}
How can I do this with a Jackson serializer that outputs elements and the json string as json?
The displayPropsJson will vary but is always valid json.
You can consider two options apart of creating a custom serializer.
Use the #JsonRawString annotation to mark a String field that
should be serialized as is without quoting of characters.
Make the ObjectMapper available inside your object instance (consider value injection) and provide a getter method that returns JsonNode deserialized from your json string value.
Here is an example demonstrating both:
public class JacksonRawString {
public static class Item {
final private ObjectMapper mapper;
public Long id = 23l;
public String name = "itemsName";
#JsonRawValue
public String displayPropsJson = "{\"bold\" : true, \"htmlAllowed\" : true, " +
"\"icon\" :\"star.jpg\" }";
public JsonNode getDisplayPropsJson2() throws IOException {
return mapper.readTree(displayPropsJson);
}
public Item(ObjectMapper mapper) {
this.mapper = mapper;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
System.out.println(
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(new Item(mapper)));
}
}
Output:
{
"id" : 23,
"name" : "itemsName",
"displayPropsJson" : {"bold" : true, "htmlAllowed" : true, "icon" :"star.jpg" },
"displayPropsJson2" : {
"bold" : true,
"htmlAllowed" : true,
"icon" : "star.jpg"
}
}
Note that the displayPropsJson2 get pretty output since it was serialized as JsonNode
Yes I am sure this can be done with custom Jackson serializer. Another thing you could do is implement JsonSerializable,
}
Yet another possibility is to implement the JsonSerializable interface
A final possibility would be to switch libraries and use Google's GSON, which makes it easy to serialize objects into and out of json.
I'm having problem to assign json data into java class.Please do help anyone,
My java class is like,
public class ListofGridRecords<T> {
public int Totalrecords;
public List<T> GridRecords;//using TraderTransaction class.
}
and TraderTransaction class is,
public class TraderTransaction {
public Date AddedTime;
public String TransactId;
public TransactStatus Status;
public String OtherPartyAccountNo;
public Double AmountPaid;
public Double AmountRecieved;
public Double ClosingBalance;
public TransactionTypes TransType;
public String Narration;
public TraderTransaction() {
super();
}
}
and my json conversion function look like,
JsonObject returndata = JsonObject.parse(responseString);
String operationresult = returndata.get("OperationResult").toString();
if (Result.values()[Integer.parseInt(operationresult)] == Result.Success) {
Gson gson = new Gson();
#SuppressWarnings("unchecked")
ListofGridRecords<TraderTransaction> traderlist =
gson.fromJson(returndata.get("ResultData").toString(), ListofGridRecords.class);
Log.i("LIST DATA:", "" + traderlist);
for (TraderTransaction trader: traderlist.GridRecords) {
HashMap<String, String> map = new HashMap<String, String>();
map.put(TRANS_FIRST_COLUMN, currentformatter.format(trader.AddedTime));
map.put(TRANS_SECOND_COLUMN, trader.TransactId);
map.put(TRANS_THIRD_COLUMN, trader.OtherPartyAccountNo);
map.put(TRANS_FOURTH_COLUMN, trader.AmountPaid.toString());
map.put(TRANS_FIFTH_COLUMN, trader.AmountRecieved.toString());
map.put(TRANS_SIXTH_COLUMN, OpenOrClosed.values()[Integer.parseInt(trader.TransType.toString())].toString());
list.add(map);
}
}
I'm getting conversion error at for (TraderTransaction trader : traderlist.GridRecords).
My Json data look like,
{
"Messages":"RESULTS_RETRIEVAL_SUCCESSFULL",
"OperationResult":0,
"ResultData":{
"GridRecords":[
{
"AddedBy":"Distributor-9787457361-Rathinavel",
"AddedTime":"2013-04-12T16:26:24.0140117",
"AmountPaid":0.0,
"AmountRecieved":10000.0,
"ClosingBalance":10000.0,
"Narration":null,
"OtherPartyAccountNo":"0102849015327675",
"Status":2,
"TransType":2,
"TransactId":"TDRF483679051236"
},
{
"AddedBy":"Distributor-9787457361-Rathinavel",
"AddedTime":"2013-04-12T16:20:54.8681857",
"AmountPaid":0.0,
"AmountRecieved":0.0,
"ClosingBalance":0.0,
"Narration":null,
"OtherPartyAccountNo":"0102849015327675",
"Status":0,
"TransType":2,
"TransactId":"TDRF706925413802"
}
],
"Totalrecords":2
},
"UpdateAvailable":"0"
}
In order to parse your JSON, I'd use a slightly different strategy. As you seem to be interested in parsing only the "ResultData", I'd create classes to wrap the response, very similar to those you have already created, namely:
public class Response {
#SerializedName("ResultData")
public ResultData resultData;
}
and,
public class ResultData {
#SerializedName("GridRecords")
public List<GridRecord> gridRecords;
#SerializedName("Totalrecords")
public int totalrecords;
}
and,
public class GridRecord {
#SerializedName("AddedTime")
public String addedTime;
#SerializedName("TransactId")
public String transactId;
//other fields...
}
and other classes if necessary...
Then, in order to parse your JSON reponse, you just have to do:
Gson gson = new Gson();
Response data = gson.fromJson(responseString, Response.class);
and you'll be able to access any field, for example:
data.resultData.gridRecords.transactId;
Note 1: If you are interested in more fields of the JSON response, you just have to add more fields to your wrap classes, according to the JSON response...
Note 2: I've changed the type of addedTime to String, instead of Date because it throws an exception for unparseable date. Anyway I usually leave the types in the Response objects as simple String and then in the class from where I retrieve the response, I do the correct formatting while creating my objects, for example, when you put the values in your Map...
Note 3: The use of the annotation #SerializedName is interesting to separate the name of a field in the JSON response and in your app, in order to follow Java naming conventions, which your attributes are not following...
Note 4: You shouldn't use public attributes in your classes. It's more recommendable to use private/protected attributes and their correspondent getters and setters...