Query a JSON file with Java-Large file - java

I am trying to parse below JSON file using java.
I need to be able to
search the file by id or name or any of the fields in the object.
search for empty values in the field as well.
The search should return entire object.
The File will be huge and the search should still be time efficient.
[
{
"id": 1,
"name": "Mark Robb",
"last_login": "2013-01-21T05:13:41 -11:30",
"email": "markrobb#gmail.com",
"phone": "12345",
"locations": [
"Germany",
"Austria"
]
},
{
"id": 2,
"name": "Matt Nish",
"last_login": "2014-02-21T07:10:41 -11:30",
"email": "mattnish#gmail.com",
"phone": "456123",
"locations": [
"France",
"Italy"
]
}
]
This is what I have tried so far using Jackson library.
public void findById(int id) {
List<Customer> customers = objectMapper.readValue(new File("src/main/resources/customers.json"), new TypeReference<List<Customer>>(){});
for(Customer customer: customers) {
if(customer.getId() == id) {
System.out.println(customer.getName());
}
}
}
I just don't think this is an efficient method for a huge JSON file(About 20000 customers in a file). And there could be multiple files. Search time should not increase linearly.
How can I make this time efficient? Should I use any other library?

The most efficient (both CPU and memory) way to parse is to use stream oriented parsing instead of object mapping. Usually, it takes a bit more code to be written, but also usually it is a good deal :) Both Gson and Jackson support such lightweight technique. Also, you should avoid memory allocation in the main/hot path to prevent GC pauses. To illustrate the idea I use a small GC-free library https://github.com/anatolygudkov/green-jelly:
import org.green.jelly.*;
import java.io.CharArrayReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
public class SelectById {
public static class Customer {
private long id;
private String name;
private String email;
public void clear() {
id = 0;
name = null;
email = null;
}
public Customer makeCopy() {
Customer result = new Customer();
result.id = id;
result.name = name;
result.email = email;
return result;
}
#Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
public static void main(String[] args) throws Exception {
final String file = "\n" +
"[\n" +
" {\n" +
" \"id\": 1,\n" +
" \"name\": \"Mark Robb\",\n" +
" \"last_login\": \"2013-01-21T05:13:41 -11:30\",\n" +
" \"email\": \"markrobb#gmail.com\",\n" +
" \"phone\": \"12345\",\n" +
" \"locations\": [\n" +
" \"Germany\",\n" +
" \"Austria\"\n" +
" ]\n" +
"},\n" +
" {\n" +
" \"id\": 2,\n" +
" \"name\": \"Matt Nish\",\n" +
" \"last_login\": \"2014-02-21T07:10:41 -11:30\",\n" +
" \"email\": \"mattnish#gmail.com\",\n" +
" \"phone\": \"456123\",\n" +
" \"locations\": [\n" +
" \"France\",\n" +
" \"Italy\"\n" +
" ]\n" +
" }\n" +
"]\n";
final List<Customer> selection = new ArrayList<>();
final long selectionId = 2;
final JsonParser parser = new JsonParser().setListener(
new JsonParserListenerAdaptor() {
private final Customer customer = new Customer();
private String currentField;
#Override
public boolean onObjectStarted() {
customer.clear();
return true;
}
#Override
public boolean onObjectMember(final CharSequence name) {
currentField = name.toString();
return true;
}
#Override
public boolean onStringValue(final CharSequence data) {
switch (currentField) {
case "name":
customer.name = data.toString();
break;
case "email":
customer.email = data.toString();
break;
}
return true;
}
#Override
public boolean onNumberValue(final JsonNumber number) {
if ("id".equals(currentField)) {
customer.id = number.mantissa();
}
return true;
}
#Override
public boolean onObjectEnded() {
if (customer.id == selectionId) {
selection.add(customer.makeCopy());
return false; // we don't need to continue
}
return true;
}
}
);
// now let's read and parse the data with a buffer
final CharArrayCharSequence buffer = new CharArrayCharSequence(1024);
try (final Reader reader = new CharArrayReader(file.toCharArray())) { // replace by FileReader, for example
int len;
while((len = reader.read(buffer.getChars())) != -1) {
buffer.setLength(len);
parser.parse(buffer);
}
}
parser.eoj();
System.out.println(selection);
}
}
It should work almost as fast as possible in Java (in case we cannot use SIMD instructions directly). To get rid of memory allocation at all (and GC pauses) in the main path, you have to replace ".toString()" (it creates new instance of String) by something reusable like StringBuilder.
The last thing which may affects overall performance is method of the file reading. And RandomAccessFile is one of the best options we have in Java. Since your encoding seems to be ASCII, just cast byte to char to pass to the JsonParser.

It should be possible to do this with Jackson. The trick is to use JsonParser to stream/parse the top-level array and then parse each record using ObjectMapper.readValue().
ObjectMapper objectMapper = new ObjectMapper();
File file = new File("customers.json");
try (JsonParser parser = objectMapper.getFactory().createParser(file))
{
//Assuming top-level array
if (parser.nextToken() != JsonToken.START_ARRAY)
throw new RuntimeException("Expected top-level array in JSON.");
//Now inside the array, parse each record
while (parser.nextToken() != JsonToken.END_ARRAY)
{
Customer customer = objectMapper.readValue(parser, Customer.class);
//Do something with each customer as it is parsed
System.out.println(customer.id + ": " + customer.name);
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Customer
{
public String id;
public String name;
public String email;
}
In terms of time efficiency it will need to still scan the entire file - not much you can do about that without an index or something fancier like parallel parsing. But it will be more memory efficient than reading the entire JSON into memory - this code only loads one Customer object at a time.
Also:
if(customer.getId() == id) {
Use .equals() for comparing strings, not ==:
if (customer.getId().equals(id)) {

You can try the Gson library. This library implements a TypeAdapter class that converts Java objects to and from JSON by streaming serialization and deserialization.
The API is efficient and flexible especially for huge files. Here is an example:
public class GsonStream {
public static void main(String[] args) {
Gson gson = new Gson();
try (Reader reader = new FileReader("src/main/resources/customers.json")) {
Type listType = new TypeToken<List<Customer>>(){}.getType();
// Convert JSON File to Java Object
List<Customer> customers = gson.fromJson(reader, listType);
List<Customer> names = customers
.stream()
.filter(c -> c.getId() == id)
.map(Customer::getName)
.collect(Collectors.toList());
} catch (IOException e) {
e.printStackTrace();
}
}
}
If you want to understand how to Override the TypeAdapter abstract class here you have and example:
public class GsonTypeAdapter {
public static void main(String args[]) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Customer.class, new customerAdapter());
builder.setPrettyPrinting();
Gson gson = builder.create();
try {
reader = new JsonReader(new FileReader("src/main/resources/customers.json"));
Customer customer = gson.fromJson(jsonString, Customer.class);
System.out.println(customer);
jsonString = gson.toJson(customer);
System.out.println(jsonString);
} catch (IOException e) {
e.printStackTrace();
}
}
}
class customerAdapter extends TypeAdapter<Customer> {
#Override
public customer read(JsonReader reader) throws IOException {
Customer customer = new customer();
reader.beginObject();
String fieldName = null;
while (reader.hasNext()) {
JsonToken token = reader.peek();
if (token.equals(JsonToken.NAME)) {
//get the current token
fieldName = reader.nextName();
}
if ("name".equals(fieldName)) {
//move to next token
token = reader.peek();
customer.setName(reader.nextString());
}
if("id".equals(fieldName)) {
//move to next token
token = reader.peek();
customer.setRollNo(reader.nextInt());
}
}
reader.endObject();
return customer;
}
#Override
public void write(JsonWriter writer, Customer customer) throws IOException {
writer.beginObject();
writer.name("name");
writer.value(customer.getName());
writer.name("id");
writer.value(customer.getId());
writer.endObject();
}
}
class Customer {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Customer[ name = " + name + ", id: " + id + "]";
}
}

Related

how to map a JSON to a java model class

I need to map JSON obj to a class and its arrays to ArrayList in Android and it should have all the children data as well. (with nested arraylists too) and i need to convert updated data list again to jsonobject
my json string is
{
"type": "already_planted",
"crops": [
{
"crop_id": 1,
"crop_name": "apple",
"crop_details": [
{
"created_id": "2017-01-17",
"questions": [
{
"plants": "10"
},
{
"planted_by": "A person"
}
]
},
{
"created_id": "2017-01-30",
"questions": [
{
"plants": "15"
},
{
"planted_by": "B person"
}
]
}
]
},
{
"crop_id": 2,
"crop_name": "Cashew",
"crop_details": [
{
"created_id": "2017-01-17",
"questions": [
{
"plants": "11"
},
{
"planted_by": "c person"
}
]
}
]
}
]
}
First of all, you need to create the class that you are going to map JSON inside.
Fortunately, there is a website that can do it for you here
secondly, you can use google Gson library for easy mapping
1. add the dependency.
dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
}
2. from your object to JSON.
MyData data =new MyData() ; //initialize the constructor
Gson gson = new Gson();
String Json = gson.toJson(data ); //see firstly above above
//now you have the json string do whatever.
3. from JSON to object .
String jsonString =doSthToGetJson(); //http request
MyData data =new MyData() ;
Gson gson = new Gson();
data= gson.fromJson(jsonString,MyData.class);
//now you have Pojo do whatever
for more information about gson see this tutorial.
If you use JsonObject, you can define your entity class as this:
public class Entity {
String type;
List<Crops> crops;
}
public class Crops {
long crop_id;
String crop_name;
List<CropDetail> crop_details;
}
public class CropDetail {
String created_id;
List<Question> questions;
}
public class Question {
int plants;
String planted_by;
}
public void convert(String json){
JsonObject jsonObject = new JsonObject(jsonstring);
Entity entity = new Entity();
entity.type = jsonObject.optString("type");
entity.crops = new ArrayList<>();
JsonArray arr = jsonObject.optJSONArray("crops");
for (int i = 0; i < arr.length(); i++) {
JSONObject crops = arr.optJSONObject(i);
Crops cps = new Crops();
cps.crop_id = crops.optLong("crop_id");
cps.crop_name = crops.optString("crop_name");
cps.crop_details = new ArrayList<>();
JsonArray details = crops.optJsonArray("crop_details");
// some other serialize codes
..........
}
}
So you can nested to convert your json string to an entity class.
Here is how I do it without any packages, this do the work for me for small use cases:
My modal class:
package prog.com.quizapp.models;
import org.json.JSONException;
import org.json.JSONObject;
public class Question {
private String question;
private String correct_answer;
private String answer_a;
private String answer_b;
private String answer_c;
private String answer_d;
public Question() {
}
public Question(String question, String answer_a, String answer_b, String answer_c, String answer_d, String correct_answer) {
this.question = question;
this.answer_a = answer_a;
this.answer_b = answer_b;
this.answer_c = answer_c;
this.answer_d = answer_d;
this.correct_answer = correct_answer;
}
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
}
public String getCorrect_answer() {
return correct_answer;
}
public void setCorrect_answer(String correct_answer) {
this.correct_answer = correct_answer;
}
public String getAnswer_a() {
return answer_a;
}
public void setAnswer_a(String answer_a) {
this.answer_a = answer_a;
}
public String getAnswer_b() {
return answer_b;
}
public void setAnswer_b(String answer_b) {
this.answer_b = answer_b;
}
public String getAnswer_c() {
return answer_c;
}
public void setAnswer_c(String answer_c) {
this.answer_c = answer_c;
}
public String getAnswer_d() {
return answer_d;
}
public void setAnswer_d(String answer_d) {
this.answer_d = answer_d;
}
#Override
public String toString() {
return "Question{" +
"question='" + question + '\'' +
", correct_answer='" + correct_answer + '\'' +
", answer_a='" + answer_a + '\'' +
", answer_b='" + answer_b + '\'' +
", answer_c='" + answer_c + '\'' +
", answer_d='" + answer_d + '\'' +
'}';
}
public static Question fromJson(JSONObject obj) throws JSONException {
return new Question(
obj.getString("question"),
obj.getString("answer_a"),
obj.getString("answer_b"),
obj.getString("answer_c"),
obj.getString("answer_d"),
obj.getString("correct_answer"));
}
}
And I have another class to get the json file from assets directory and mapped JsonObject to my model class Question:
package prog.com.quizapp.utils;
import android.content.Context;
import android.util.Log;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import prog.com.quizapp.models.Question;
public class JsonSqlQueryMapper {
private Context mContext;
public JsonSqlQueryMapper(Context context) {
this.mContext = context;
}
private static final String TAG = "JsonSqlQueryMapper";
public JSONObject loadJSONFromAsset() {
String json = null;
try {
InputStream is = mContext.getAssets().open("quiz_app.json");
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
} catch (IOException ex) {
ex.printStackTrace();
return null;
}
try {
JSONObject quizObject = new JSONObject(json).getJSONObject("quiz");
return quizObject;
} catch (Exception e) {
Log.d(TAG, "loadJSONFromAsset: " + e.getMessage());
return null;
}
}
public ArrayList<Question> generateInsertQueryForJsonObjects() {
ArrayList<Question> questions = new ArrayList<>();
JSONObject jsonObject = loadJSONFromAsset();
try {
Iterator<String> iter = jsonObject.keys();
while (iter.hasNext()) {
String key = iter.next();
JSONObject value = jsonObject.getJSONObject(key);
Question question = Question.fromJson(value.getJSONObject("question_two"));
questions.add(question);
Log.d(TAG, "generateInsertQueryForJsonObjects: " + question.getAnswer_a());
}
} catch (Exception e) {
e.printStackTrace();
}
return questions;
}
}
And in my MainActivity -> onCreate:
JsonSqlQueryMapper mapper = new JsonSqlQueryMapper(MainActivity.this);
mapper.generateInsertQueryForJsonObjects();
To check that everything working as I want. Here is the json file if you want to check https://github.com/Blasanka/android_quiz_app/blob/sqlite_db_app/app/src/main/assets/quiz_app.json
Regards!

Iterate ArrayList

I want to know the best way to iterate this ArrayList, this ArrayList comes from a Response from an API, this is the ArrayList:
The problem is that i dont know how to get the "id" and the "value" from the loop,
i know the arraylist size but i dont have any idea how to print the "Keys" and "Values" from this Array
for(int i=1; i <= contacts.size(); i++) {
//Example System.out.print(contacts[i]->id);
//Example System.out.print(contacts[i]->contact_name) ;
//Example System.out.print(contacts[i]->numbers);
//Example System.out.print(contacts[i]->emails);
//I want to print id and value
//
}
In onResponse i call this fucntion for example:
ServerResponse resp = response.body();
functionExample((ArrayList) resp.getResponse());
The functionExample have an ArrayList as parameter.
This is my result from my resp.getResponse():
This is my json from the API:
{
"result": "success",
"message": "Lista de Contactos",
"response": [
{
"id": 1,
"contact_name": "EDIFICADORA JUANA",
"numbers": "{24602254,55655545}",
"emails": "{oipoa#gmaio.com,rst008#guan.com}"
},
{
"id": 2,
"contact_name": "LA MEJOR",
"numbers": "{25445877,25845877}",
"emails": "{AMEJOR#GMAIL.COM}"
}
]
}
I appreciate any help.
public void FunctionExample(ArrayList contacts) {
for(int i=0; i < contacts.size(); i++) {
LinkedTreeMap<String, Object> map = (LinkedTreeMap<String, Object>) contacts.get(i);
map.containsKey("id");
String id = (String) map.get("id");
map.containsKey("contact_name");
String contact_name = (String) map.get("contact_name");
map.containsKey("numbers");
String numbers = (String) map.get("numbers");
numbers.replace("{","").replace("}","");
map.containsKey("emails");
String emails = (String) map.get("emails");
emails.replace("{","").replace("}","");
Snackbar.make(getView(), id, Snackbar.LENGTH_LONG).show();
Snackbar.make(getView(), contact_name, Snackbar.LENGTH_LONG).show();
Snackbar.make(getView(), numbers, Snackbar.LENGTH_LONG).show();
Snackbar.make(getView(), emails, Snackbar.LENGTH_LONG).show();
}
}
Try this..It will give arrayList of id's
JSONObject object=new JSONObject(response);
JSONArray array= null;
try {
array = object.getJSONArray("response");
} catch (JSONException e) {
e.printStackTrace();
}
ArrayList<String> idArray=new ArrayList<>();
for(int i=0;i< array.length();i++)
{
idArray.add(getJSONObject(i).getString("id"));
}
Try this way if you are using ArrayList<TreeMap<String, String>> contacts;
for(TreeMap<String,String> contact : contacts){
String id = contact.getValue("id");
}
I would strongly encourage you to use e.g. Jackson to map your JSON response to a proper object. Consider following example:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JacksonTest {
private static final String JSON = "{\n" +
"\"result\": \"success\",\n" +
"\"message\": \"Lista de Contactos\",\n" +
"\"response\": [\n" +
" {\n" +
" \"id\": 1,\n" +
" \"contact_name\": \"EDIFICADORA JUANA\",\n" +
" \"numbers\": \"{24602254,55655545}\",\n" +
" \"emails\": \"{oipoa#gmaio.com,rst008#guan.com}\"\n" +
" },\n" +
" {\n" +
" \"id\": 2,\n" +
" \"contact_name\": \"LA MEJOR\",\n" +
" \"numbers\": \"{25445877,25845877}\",\n" +
" \"emails\": \"{AMEJOR#GMAIL.COM}\"\n" +
" }\n" +
" ]\n" +
"}";
#Test
public void testParsingJSONStringWithObjectMapper() throws IOException {
//given:
final ObjectMapper objectMapper = new ObjectMapper();
//when:
final Response response = objectMapper.readValue(JSON, Response.class);
//then:
assert response.getMessage().equals("Lista de Contactos");
//and:
assert response.getResult().equals("success");
//and:
assert response.getResponse().get(0).getId().equals(1);
//and:
assert response.getResponse().get(0).getContactName().equals("EDIFICADORA JUANA");
//and:
assert response.getResponse().get(0).getEmails().equals(Arrays.asList("oipoa#gmaio.com", "rst008#guan.com"));
//and:
assert response.getResponse().get(0).getNumbers().equals(Arrays.asList(24602254, 55655545));
}
static class Response {
private String result;
private String message;
private List<Data> response = new ArrayList<>();
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public List<Data> getResponse() {
return response;
}
public void setResponse(List<Data> response) {
this.response = response;
}
}
static class Data {
private String id;
#JsonProperty("contact_name")
private String contactName;
private String numbers;
private String emails;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getContactName() {
return contactName;
}
public void setContactName(String contactName) {
this.contactName = contactName;
}
public List<Integer> getNumbers() {
return Stream.of(numbers.replaceAll("\\{", "")
.replaceAll("}", "")
.split(","))
.map(Integer::valueOf)
.collect(Collectors.toList());
}
public void setNumbers(String numbers) {
this.numbers = numbers;
}
public List<String> getEmails() {
return Arrays.asList(emails.replaceAll("\\{", "")
.replaceAll("}", "")
.split(","));
}
public void setEmails(String emails) {
this.emails = emails;
}
}
}
In this example I used same JSON response you receive and jackson-core library (http://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core/2.8.9) for mapping String to a POJOs (instead of String you can use InputStream, byte[] etc.). There are two POJOs: Response and Data. Response aggregates a list of Data objects. Additionally, Data's getEmails() and getNumbers() methods parse your input String to a list of expected objects. For example if you call setNumbers("{24602254,55655545}") then getNumbers() will return a list of Integers (you can use any numeric type instead) like [24602254, 55655545].
Other suggestions are also valid, e.g. iterating over collection of TreeMaps or JSONObjects. In this example we limit our focus to deal with Java objects with specific types instead of dealing with primitives like Object class for example.
The final solution also depends on your runtime environment. In this case you will have to add jackson-core dependency - it makes more sense if your project already uses Jackson for other reasons.
If you are using Set< Map< String, String>> set;
set.stream().forEach(map -> {
System.out.print("Id:" + map.get("id") + "ContactName:" + map.get("contact_name"));
});
Try this loop to extract every value from ArrayList of yours
List<LinkedTreeMap> list = new ArrayList<LinkedTreeMap>(); //assign result from API to list
for(LinkedTreeMap<String,String> contact : list){
for(String id : contact.keySet()){
if(id.equalsIgnoreCase("id")){
System.out.println("ID: "+ contact.get(id));
}else if(id.equalsIgnoreCase("contact_name")){
System.out.println("Contact Name: "+ contact.get(id));
}else{ //if it is list of numbers or e-mails
String result = contact.get(id);
result = result.replaceAll("{|}", ""); //removing { }
String[] array = result.split(",");
System.out.println(id+": "); // this will be either numbers or e-mails
//now iterating to get each value
for(String s : array){
System.out.println(s);
}
}
}
}

Extracting the value of an element from a JSON

I'm trying to make a test where I get some documents based on the id of the batch they belong to. More specifically, I want to check that a specific batchPublicId is in the response body. I am using okhttp for the test.
This a shorter version of the json:
{
"_embedded": {
"invoices": [
{
"type": "INVOICE",
"publicId": "27bc8426-17cf-4fe5-9278-64108ae05e4b",
"deliveryStatus": null,
"processingStatus": "INITIATED",
"batchPublicId": "0000000000000000000000001"
}
]
}
}
I'm new to json and this is how far I got with the problem:
String invoicesJsonData = response.body().string();
JSONObject invoicesJsonObject = new JSONObject(invoicesJsonData);
Assert.assertTrue(invoicesJsonObject.getJSONObject("_embedded") !=null && invoicesJsonObject.getJSONObject("_embedded").has("invoices"));
I would like to verify that batchPublicId has the value mentioned in the json. Is there a way to do this? Thank you.
String invoicesJsonData = response.body().string();
JSONObject invoicesJsonObject = new JSONObject(invoicesJsonData);
JSONObject invoicesJsonObject1 = invoicesJsonObject.getJSONObject("_embedded");
JSONArray f2=invoicesJsonObject1.getJSONArray("invoices");
for(int i=0;i<f2.length();i++){
JSONObject obj=f2.getJSONObject(i);
if(obj.get("batchPublicId")!=null){
System.out.println(obj.get("batchPublicId"));
}
You can do something like this,Which worked out for me sometimes back.
String invoicesJsonData = response.body().string();
JSONObject invoicesJsonObject = new JSONObject(invoicesJsonData);
JSONObject invoicesJsonObject = json.getJSONObject("invoicesJsonObject");
String batchPublicId = invoicesJsonObject.getString("batchPublicId");
System.out.println( "batchPublicId: " + batchPublicId );
if(batchPublicId !=null){
// do something
}
Not sure about the syntax.Giving you a hint.
you can check any keys is there in json object or not like below :
if(jsonObject1.has("batchPublicId")){
String batchPublicId = jsonObject1.optString("batchPublicId");
Log.i(getClass().getSimpleName(), "batchPublicId=" + batchPublicId);}
has method is used to find any key is there in jsonobject or not.
In my opinion, a better approach for this would be to create a POJO from this JSON string, and extract the information you need using simply the getters
For example:
Wrapper class:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName(value = "_embedded")
public class Embeded {
#JsonProperty("invoices")
private List<Invoice> invoices;
public Embeded() {}
public List<Invoice> getInvoices() {
return invoices;
}
public void setInvoices(List<Invoice> invoices) {
this.invoices = invoices;
}
}
Invoice class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Invoice {
#JsonProperty("type")
private String type;
#JsonProperty("publicId")
private String publicId;
#JsonProperty("deliveryStatus")
private String deliveryStatus;
#JsonProperty("processingStatus")
private String processingStatus;
#JsonProperty("batchPublicId")
private String batchPublicId;
public Invoice() {}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getPublicId() {
return publicId;
}
public void setPublicId(String publicId) {
this.publicId = publicId;
}
public String getDeliveryStatus() {
return deliveryStatus;
}
public void setDeliveryStatus(String deliveryStatus) {
this.deliveryStatus = deliveryStatus;
}
public String getProcessingStatus() {
return processingStatus;
}
public void setProcessingStatus(String processingStatus) {
this.processingStatus = processingStatus;
}
public String getBatchPublicId() {
return batchPublicId;
}
public void setBatchPublicId(String batchPublicId) {
this.batchPublicId = batchPublicId;
}
}
Test:
public void json_test() throws JsonParseException, JsonMappingException, IOException {
String json = "{"
+ "\"_embedded\": {"
+ "\"invoices\": ["
+ "{"
+ "\"type\": \"INVOICE\","
+ "\"publicId\": \"27bc8426-17cf-4fe5-9278-64108ae05e4b\","
+ "\"deliveryStatus\": null,"
+ "\"processingStatus\": \"INITIATED\","
+ "\"batchPublicId\": \"0000000000000000000000001\""
+ "}"
+ "]"
+ "}"
+ "}";
ObjectMapper mapper = new ObjectMapper();
mapper.configure(Feature.UNWRAP_ROOT_VALUE, true);
List<Invoice> invoices = mapper.readValue(json, Embeded.class).getInvoices();
Assert.assertTrue(StringUtils.equals(invoices.get(0).getBatchPublicId(), "0000000000000000000000001"));
}
If I understand your right, you just need to call:
Assert.assertTrue(invoicesJsonObject.getString("batchPublicId").equals("0000000000000000000000001"));"
If you want to create a test for JSON Validation, you can use the JSONAssert.
JSONAsset give the method assertEquals, that compare two json structures, strict identic or not.
final String expected_result = YOUR_EXPECTED_RESULT;
JSONAssert.assertEquals(YOUR_EXPECTED_JSON_RESULT, RESULT_FROM_RESPONSE_BODY, false);
The last boolean parameter defines if you want an strict comparation or just compare if your expected result is in result from response.

Generic tuple de-serialization in Jackson

It so happens that I need to support in Java JSON data coming from external data sources. There is one common pattern. It's an array containing fixed number of elements of certain different types. We call it tuple. Here is my example of de-serialization for 3-element tuple with particular expected types of elements using FasterXML Jackson:
public class TupleTest {
public static void main(String[] args) throws Exception {
String person = "{\"name\":\"qqq\",\"age\":35,\"address\":\"nowhere\",\"phone\":\"(555)555-5555\",\"email\":\"super#server.com\"}";
String jsonText = "[[" + person + ",[" + person + "," + person + "],{\"index1\":" + person + ",\"index2\":" + person + "}]]";
ObjectMapper om = new ObjectMapper().registerModule(new TupleModule());
List<FixedTuple3> data = om.readValue(jsonText, new TypeReference<List<FixedTuple3>>() {});
System.out.println("Deserialization result: " + data);
System.out.println("Serialization result: " + om.writeValueAsString(data));
}
}
class Person {
public String name;
public Integer age;
public String address;
public String phone;
public String email;
#Override
public String toString() {
return "Person{name=" + name + ", age=" + age + ", address=" + address
+ ", phone=" + phone + ", email=" + email + "}";
}
}
class FixedTuple3 {
public Person e1;
public List<Person> e2;
public Map<String, Person> e3;
#Override
public String toString() {
return "Tuple[" + e1 + ", " + e2 + ", " + e3 + "]";
}
}
class TupleModule extends SimpleModule {
public TupleModule() {
super(TupleModule.class.getSimpleName(), new Version(1, 0, 0, null, null, null));
setSerializers(new SimpleSerializers() {
#Override
public JsonSerializer<?> findSerializer(SerializationConfig config,
JavaType type, BeanDescription beanDesc) {
if (isTuple(type.getRawClass()))
return new TupleSerializer();
return super.findSerializer(config, type, beanDesc);
}
});
setDeserializers(new SimpleDeserializers() {
#Override
public JsonDeserializer<?> findBeanDeserializer(JavaType type,
DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {
Class<?> rawClass = type.getRawClass();
if (isTuple(rawClass))
return new TupleDeserializer(rawClass);
return super.findBeanDeserializer(type, config, beanDesc);
}
});
}
private boolean isTuple(Class<?> rawClass) {
return rawClass.equals(FixedTuple3.class);
}
public static class TupleSerializer extends JsonSerializer<Object> {
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
try {
jgen.writeStartArray();
for (int i = 0; i < 3; i++) {
Field f = value.getClass().getField("e" + (i + 1));
Object res = f.get(value);
jgen.getCodec().writeValue(jgen, res);
}
jgen.writeEndArray();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
public static class TupleDeserializer extends JsonDeserializer<Object> {
private Class<?> retClass;
public TupleDeserializer(Class<?> retClass) {
this.retClass = retClass;
}
public Object deserialize(JsonParser p, DeserializationContext ctx) throws IOException, JsonProcessingException {
try {
Object res = retClass.newInstance();
if (!p.isExpectedStartArrayToken()) {
throw new JsonMappingException("Tuple array is expected but found " + p.getCurrentToken());
}
JsonToken t = p.nextToken();
for (int i = 0; i < 3; i++) {
final Field f = res.getClass().getField("e" + (i + 1));
TypeReference<?> tr = new TypeReference<Object>() {
#Override
public Type getType() {
return f.getGenericType();
}
};
Object val = p.getCodec().readValue(p, tr);
f.set(res, val);
}
t = p.nextToken();
if (t != JsonToken.END_ARRAY)
throw new IOException("Unexpected ending token in tuple deserializer: " + t.name());
return res;
} catch (IOException ex) {
throw ex;
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
}
But this approach means I have to make new class every time I face new type configuration in tuple of certain size. So I wonder if there is any way to define deserializer dealing with generic typing. So that it will be enough to have one tuple class per tuple size. For instance my generic tuple of size 3 could be defined like:
class Tuple3 <T1, T2, T3> {
public T1 e1;
public T2 e2;
public T3 e3;
#Override
public String toString() {
return "Tuple[" + e1 + ", " + e2 + ", " + e3 + "]";
}
}
And usage of it would look like:
List<Tuple3<Person, List<Person>, Map<String, Person>>> data =
om.readValue(jsonText,
new TypeReference<List<Tuple3<Person, List<Person>, Map<String, Person>>>>() {});
Is it something doable or not?
Ok. So... there may be a simpler way to do "tuple"-style. You can actually annotate POJOs to be serialized as arrays:
#JsonFormat(shape=JsonFormat.Shape.ARRAY)
#JsonPropertyOrder({ "name", "age" }) // or use "alphabetic"
public class POJO {
public String name;
public int age;
}
and if so, they'll get written as arrays, read from arrays.
But if you do what to handle custom generic types, you probably need to get type parameters resolved. This can be done using TypeFactory, method findTypeParameters(...). While this may seem superfluous, it is needed for general case if you sub-type (if not, JavaType actually has accessors for direct type parameters).
Yes, you must use Reflection to get ALL FIELDS, not to get the known fields by name.

JSON formatting for a Java Server

I am trying to read JSON string using gson into a Java program. In the sample code below - the Java program has 3 object classes. The data in the json string will have a variable number of object instances of each class. I have tried to create a sample JSON - to parse .. but had problems parsing the various objects.
Is this the right way to consume a json string or can it be done in a different way.. How would you parse a json with variable objects of different classes. Thanks,
package newpackage;
import java.util.ArrayList;
import com.google.gson.Gson;
public class jsonsample {
public static void main(String[] args) {
String jsonstring = "{'TableA':[{'field_A1':'A_11'},{'field_A1':'A_12'}]}"
+ ",{'TableB':[{'field_B1':'B_11','field_B2':'B_12','field_B3':['abc','def','ghi']},"
+ "{'field_B1':'B_21','field_B2':'B_Field22','field_B3':['mno','pqr','xyz']}]"
+ ",{'TableC':[{'field_C1':'C_11','field_C2':'C_12','field_C3':'C_13'},"
+ "{'field_C1':'C_21','field_C2':'C_22','field_C3':'C_23'},{'field_C1':'C_31','field_C2':'C_32','field_C3':'C_33'}]}";
jsonstring = jsonstring.replace('\'', '"');
}
public class TableA {
String field_A1;
public TableA(String a){
this.field_A1 = a;
}
#Override
public String toString() {
return ("Table A" + " " + this.field_A1);
}
}
public class TableB {
String field_B1;
String field_B2;
ArrayList<String> field_B3 = new ArrayList<String>();
public TableB(String a, String b, ArrayList<String> c){
this.field_B1 = a;
this.field_B2 = b;
this.field_B3 = c;
}
#Override
public String toString() {
return ("Table B" + " " + this.field_B1+ " " + this.field_B2);
}
}
public class TableC {
String field_C1;
String field_C2;
String field_C3;
public TableC(String a, String b, String c){
this.field_C1 = a;
this.field_C2 = b;
this.field_C3 = c;
}
#Override
public String toString() {
return ("Table C" + " " + this.field_C1 + " " + this.field_C2 + " " + this.field_C3);
}
}
}
First of all you have to decide what is your base json structure ? Max identifiers, max values, max objects,max arrays...
Create your full json structure with texteditor or http://www.jsoneditoronline.org/ or http://jsonlint.com/ etc.
Let's think this is my full json structure:
{
"array": [
1,
2,
3
],
"boolean": true,
"null": null,
"number": 123,
"object": {
"a": "b",
"c": "d",
"e": "f"
},
"string": "Hello World"
}
Create your Java Classes as like as your json identifiers. You can use http://json2csharp.com/ convert to Java.
And these are my Java Classes:
public class Object
{
public string a { get; set; }
public string c { get; set; }
public string e { get; set; }
}
public class RootObject
{
public ArrayList<int> array { get; set; }
public Boolean boolean { get; set; }
public Object #null { get; set; }
public int number { get; set; }
public Object #object { get; set; }
public string #string { get; set; }
}
Create your DAO for convert these to structure to them.
For Java;
String data = "jsonString";
RootObject root = new GsonBuilder().create().fromJson(data, RootObject.class);
For Json;
Gson gson = new GsonBuilder().setDateFormat("dd/MM/yyyy").create();
String json = gson.toJson(obj);
Your JSON-string seems incorrect to me. Let me propose the following:
public static void main(String args[]) {
String jsonstring = "["
+ "{'TableA':[{'field_A1':'A_11'},{'field_A1':'A_12'}]}"
+ ",{'TableB':[{'field_B1':'B_11','field_B2':'B_12','field_B3':['abc','def','ghi']},"
+ "{'field_B1':'B_21','field_B2':'B_Field22','field_B3':['mno','pqr','xyz']}]}"
+ ",{'TableC':[{'field_C1':'C_11','field_C2':'C_12','field_C3':'C_13'},"
+ "{'field_C1':'C_21','field_C2':'C_22','field_C3':'C_23'},{'field_C1':'C_31','field_C2':'C_32','field_C3':'C_33'}]}"
+ "]";
jsonstring = jsonstring.replace('\'', '"');
Gson gson = new Gson();
JsonParser parser = new JsonParser();
JsonArray array = parser.parse(jsonstring).getAsJsonArray();
for (JsonElement jsonElement : array) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
Map.Entry<String,JsonElement> table = jsonObject.entrySet().iterator().next();
String tableName = table.getKey();
JsonElement rows = table.getValue();
try {
Class<?> rowClass = Class.forName("[Lnewpackage." + tableName + ";"); // explanation see below this code snippet
// rowClass is an array class!
Object[] parsedRows = gson.fromJson(rows, rowClass);
// do something with parsedRows
for (Object x : parsedRows) {
System.out.println(x);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Assuming a "table definition" consists of a property named as the class ob the objects in the table, with the objects as array value of that property.
Explanation of Class.forName("[Lnewpackage." + tableName + ";")
This retrieves the Class instance for the array type of a class located in the package newpackage, e.g. newpackage.TableA[] (note the []). Class.forName("A") returns the instance representing the class A. Class.forName("[LA;") returns the instance representing the "class" of an array of As. Using it as a parameter for fromJson(...) it results in the parsing of a JSON array of A-objects.
This is the code - that works based on #hurricane suggestion.
package newpackage;
import java.util.List;
import com.google.gson.*;
public class jsonsample {
public static void main(String[] args) throws ClassNotFoundException {
String jsonstring = "{'TableA':["
+ "{'field_A1':'A_11'},"
+ "{'field_A1':'A_12'}"
+ "],"
+ "'TableB':["
+ "{'field_B1':'B_11','field_B2':'B_12','field_B3':['abc','def']},"
+ "{'field_B1':'B_21','field_B2':'B_22','field_B3':['mno','xyz']}"
+ "],"
+ "'TableC':["
+ "{'field_C1':'C_11','field_C2':'C_12','field_C3':'C_13'},"
+ "{'field_C1':'C_21','field_C2':'C_22','field_C3':'C_23'}"
+ "]}";
jsonstring = jsonstring.replace('\'', '"');
RootObject root = new GsonBuilder().create().fromJson(jsonstring, RootObject.class);
for (int i=0; i < root.TableA.size(); i++){
System.out.println(root.TableA.get(i));
}
for (int i=0; i < root.TableB.size(); i++){
System.out.println(root.TableB.get(i));
}
for (int i=0; i < root.TableC.size(); i++){
System.out.println(root.TableC.get(i));
}
}
public class TableA
{
public String field_A1;
#Override
public String toString() {
return ("Table A" + " " + this.field_A1);
}
}
public class TableB{
public String field_B1;
public String field_B2;
public List<String> field_B3;
#Override
public String toString() {
return ("Table B" + " " + this.field_B1 + " " + this.field_B2 + " " + this.field_B3);
}
}
public class TableC{
public String field_C1;
public String field_C2;
public String field_C3;
#Override
public String toString() {
return ("Table C" + " " + this.field_C1 + " " + this.field_C2 + " " + this.field_C3);
}
}
public class RootObject{
public List<TableA> TableA;
public List<TableB> TableB;
public List<TableC> TableC;
}
}
The output for the above is:
Table A A_11
Table A A_12
Table B B_11 B_12 [abc, def]
Table B B_21 B_22 [mno, xyz]
Table C C_11 C_12 C_13
Table C C_21 C_22 C_23

Categories