I haven't worked with JSON data before, thus the question.
I've the following JSON object in a file.
{
"courses": [
{ "id":998", "name":"Java Data Structures", "teacherId":"375" },
{ "id":"999", "name":"Java Generics", "teacherId":"376" }
],
"teachers": [
{ "id":"375", "firstName":"Amiyo", "lastName":"Bagchi"},
{ "id":"376", "firstName":"Dennis", "lastName":"Ritchie"}
]
}
Here are my model Objects.
public class Course {
private int _id;
private String _name;
private Teacher _teacher;
}
public class Teacher {
private int _id;
private String _firstName;
private String _lastName;
}
My task is to read the JSON Objects and return a list of Model objects.
I've imported the simple.JSON family of jar and here's my code that reads the file.
FileReader reader = new FileReader(path);
JSONParser parser = new JSONParser();
Object obj = parser.parse(reader);
JSONObject jsonObject = (JSONObject) obj;
My question is,
How do I parse the JSON document into my Model objects?
If the input file is JSON but of a different format how do I throw exception/handle the anomaly?
Any help appreciated.
UPDATE I suggest you use JSON parser to parse the data:
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
class Course {
public int _id;
public String _name;
public Teacher _teacher;
private Course(int id, String name, Teacher teacher){
this._id = id;
this._name = name;
this._teacher = teacher;
}
public Course() {
}
}
class Teacher {
public int _id;
public String _firstName;
public String _lastName;
private Teacher(int id, String fname, String lname){
this._id = id;
this._firstName = fname;
this._lastName = lname;
}
public Teacher(){
}
}
public class jsontest {
public static void main(String[] args) throws JSONException, IOException {
// String JSON_DATA = "{\n"+
// " \"courses\": [\n"+
// " { \"id\":\"998\", \"name\":\"Java Data Structures\", \"teacherId\":\"375\" },\n"+
// " { \"id\":\"999\", \"name\":\"Java Generics\", \"teacherId\":\"376\" }\n"+
// "\n"+
// " ],\n"+
// " \"teachers\": [\n"+
// " { \"id\":\"375\", \"firstName\":\"Amiyo\", \"lastName\":\"Bagchi\"},\n"+
// " { \"id\":\"376\", \"firstName\":\"Dennis\", \"lastName\":\"Ritchie\"} \n"+
// " ]\n"+
// "}\n"+
// "";
// read json file into string
String JSON_DATA = new String(Files.readAllBytes(Paths.get("path_to_json_file")), StandardCharsets.UTF_8);
// using a JSON parser
JSONObject obj = new JSONObject(JSON_DATA);
// parse "teachers" first
List<Teacher> listCourses = new ArrayList<Teacher>();
List<JSONObject> listObjs = parseJsonData(obj,"teachers");
for (JSONObject c: listObjs) {
Teacher teacher = new Teacher();
teacher._id = c.getInt("id");
teacher._firstName = c.getString("firstName");
teacher._lastName = c.getString("lastName");
listCourses.add(teacher);
}
// parse "courses" next
List<Course> resultCourses = new ArrayList<Course>();
List<JSONObject> listObjs2 = parseJsonData(obj, "courses");
for (JSONObject c: listObjs2) {
Course course = new Course();
course._id = c.getInt("id");
course._name = c.getString("name");
int teacherId = c.getInt("teacherId");
HashMap<String, Teacher> map = new HashMap<String, Teacher>();
for (Teacher t: listCourses){
map.put(Integer.toString(t._id), t);
}
course._teacher = map.get(Integer.toString(teacherId));
resultCourses.add(course);
}
}
public static List<JSONObject> parseJsonData(JSONObject obj, String pattern)throws JSONException {
List<JSONObject> listObjs = new ArrayList<JSONObject>();
JSONArray geodata = obj.getJSONArray (pattern);
for (int i = 0; i < geodata.length(); ++i) {
final JSONObject site = geodata.getJSONObject(i);
listObjs.add(site);
}
return listObjs;
}
}
Output:
BTW: The json data in the example has one value whose double quotes are not in pairs. To proceed, it must be fixed.
You should try using Jackson as the JSON parsing library instead. There is a lot more support and features that come with it.
In your case, a couple of annotations to map the JSON properties to the Java fields should be sufficient.
https://github.com/FasterXML/jackson-annotations
https://github.com/FasterXML/jackson-databind
UPDATE: Some code, to show just much better this can be done with Jackson.
public class Course {
#JsonProperty("id")
private int _id;
#JsonProperty("name")
private String _name;
#JsonProperty("teacher")
private Teacher _teacher;
// ...public getters and setters
}
public class Teacher {
#JsonProperty("id")
private int _id;
#JsonProperty("firstName")
private String _firstName;
#JsonProperty("lastName")
private String _lastName;
// ...public getters and setters
}
// Container class to conform to JSON structure
public class CoursesDto {
private List<Teacher> teachers;
private List<Course> courses;
}
// In your parser place
ObjectMapper mapper = new ObjectMapper();
FileReader reader = new FileReader(path);
CoursesDto dto = mapper.readValue(reader, CoursesDto.class);
The #JsonProperty annotations tell Jackson what JSON key should be used to deserialize. They are not necessary if the property names match the JSON keys. That means that if you remove the leading underscore from your property names, this would work without annotations. Also, Jackson will default to using public fields and getter/setter methods. This means that you can keep your fields prefixed by _ as long as the getter/setter don't have it (setFirstName(String firstName)).
Related
I have the following entity class:
public class Conversation {
private String id;
private String ownerId;
private Long creationDate;
public Conversation(String id, String ownerId, Long creationDate){
this.id = id;
this.ownerId = ownerId;
this.creationDate = creationDate;
}
}
On other submodule through an external service, on each insertion, I recive a map of the following entities:
public class AttributeValue {
private Sring s; //string attribute
private String n; //number attribute
public String getS() {
return this.s;
}
public String getN() {
return this.n;
}
public AttributeValue(String s, String n){
this.s = s;
this.n = n;
}
}
//Example if I insert this conversation: new Conversation("1", "2", 1623221757971)
// I recive this map:
Map<String, AttributeValue> insertStream = Map.ofEntries(
entry("id", new AttributeValue("1", null)),
entry("ownerId", new AttributeValue("2", null)),
entry("creationDate", new AttributeValue(null, "1623221757971"))
);
To read the ownerId field from the map, I have to do this:
String ownerId = insertStream.get("ownerId").getS();
My question is, instead of have to write: insertStream.get("ownerId"), exists any way through Reflection to read the name of the field from the entity (Conversation.ownerId)?
This is because we want to mantain the submodule and If we make a change on the entitity, for example change ownerId for ownerIdentifier, the submodule shows a compilation error or is changed automatically.
Is this what you want? Field#getName()
Example code:
Field[] conversationFields = Conversation.class.getDeclaredFields();
String field0Name = conversationFields[0].getName();
Depending on the JVM used, field0Name can be "id". You can also use Class#getFields(), this method includes all Fields that are accessible in this class (super class's fields).
Another option (not using reflection) would be to refactor your code.
import java.util.Map;
import java.util.HashMap;
public class Conversation {
public static String[] names = {
"id", "ownerId", "creationDate"
};
private Map<String, Object> data = new HashMap<String,Object>();
public Conversation(Object... data) {
if(data.length!=names.length)
throw new IllegalArgumentException("You need to pass "+names.length+" arguments!");
for(int i=0; i<names.length; i++)
data.put(names[i],data[i]);
}
public Map<String,Object> getData() { return data; }
// You can pass "id"/"ownerId" or names[0]/names[1]
public String getString(String key) {
return (String)data.get(key);
}
// You can pass "creationDate" or names[2]
public long getLong(String key) {
return (long)data.get(key);
}
}
You could then create Conversation Objects like before:
Conversation c = new Conversation("myId","myOwnerId",123456789L);
You could also add public static String fields like ID="id", but changing the value of a field will never change the field's name.
I'm trying to create POJOs for the following JSON structure. The Fields node is easy enough to wire up, but I'm unsure how to use annotations to wire up the Description node. If I had been defining the JSON structure for that node, I'd have create an JsonArray of JsonObjects, which would make the java class easy, but since I didn't, I need to figure out how to serialize the structure below:
{
"Fields": {
"Required": ["ftp.hostname"],
"Optional": ["ftp.rootDirectory"]
},
"Description": {
"ftp.hostname": {
"label": "SFTP Hostname",
"description": "SFTP server hostname or IP address"
},
"ftp.rootDirectory": {
"label": "Root Directory",
"description": "The root path on the Data Store accessible by this connector"
}
}
}
Note that the nodes in the Description object have names that correlate to the values defined in the Fields node, which means their node names can vary from payload to payload.
The class for the Fields node:
public class FieldDetails {
public static final String REQUIRED = "Required";
public static final String OPTIONAL = "Optional";
#JsonProperty(value = REQUIRED, required = true)
private List<String> required;
#JsonProperty(value = OPTIONAL, required = true)
private List<String> optional;
}
And what I have so far for the entire object:
public class FieldDefinitions {
public static final String FIELDS = "Fields";
public static final String DESCRIPTION = "Description";
#JsonProperty(value = FIELDS, required = true)
private FieldDetails fields;
#JsonProperty(value = DESCRIPTION , required = true)
private ??? descriptions;
}
Generally, you can always map any JSON object to Map<String, Object>. If JSON is complicated with many nested objects, Jackson will automatically pick correct type: Map for objects and List for arrays.
You can also declare class like below for Description properties.
class Description {
private String label;
private String description;
// getters, setters, toString
}
The whole Description is a big JSON which you can map to Map<String, Description>. So, it could look like below:
class FieldDefinitions {
public static final String FIELDS = "Fields";
public static final String DESCRIPTION = "Description";
#JsonProperty(value = FIELDS, required = true)
private FieldDetails fields;
#JsonProperty(value = DESCRIPTION, required = true)
private Map<String, Description> descriptions;
// getters, setters, toString
}
Rest is the same. Example app:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.List;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
File json = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
FieldDefinitions fields = mapper.readValue(json, FieldDefinitions.class);
System.out.println("Required");
fields.getFields().getRequired().forEach(r ->
System.out.println(r + " = " + fields.getDescriptions().get(r)));
System.out.println("Optional");
fields.getFields().getOptional().forEach(r ->
System.out.println(r + " = " + fields.getDescriptions().get(r)));
}
}
For given JSON payload prints:
Required
ftp.hostname = Description{label='SFTP Hostname', description='SFTP server hostname or IP address'}
Optional
ftp.rootDirectory = Description{label='Root Directory', description='The root path on the Data Store accessible by this connector'}
That's the structure.
public class FieldDefinitions {
#JsonProperty("Fields")
public FieldDetails fields = new FieldDetails();
#JsonProperty("Description")
public Map<String, Property> properties = new HashMap<>();
}
public class FieldDetails {
#JsonProperty("Required")
public List<String> required = new ArrayList<>();
#JsonProperty("Optional")
public List<String> optional = new ArrayList<>();
}
public class Property {
public String label;
public String description;
}
public class User {
String name;
String id;
Address[] address;
....
....
//40 more fields
}
public class Address {
String street;
String city;
String state;
}
I have a List and I need to convert it to json with only few fields.
public String fetchUsers(List<Users> users, List<String> fields) {
//fetch the list of users having the specific fields in the list and return as json
}
fields = ["name", "address.state"]
I can remove fields in json.... But, I need to keep restricted fields as per values passed in the method. I can use any third party lib as well.
Use com.google.gson.Gson library for serialize your objects into json and you have to create ExclusionStrategy for your object for fields which you want to skip or not. create a GsonBuilder object from that and parse your object from it.
this is perfectly working fine.
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ABC {
public class User {
public String name;
public String id;
public Address[] address;
}
public class Address {
public String street;
public String city;
public String state;
}
public static ExclusionStrategy createStrategy(List<String> fields) {
return new ExclusionStrategy() {
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
if (fields.stream().anyMatch(e -> e.equals(fieldAttributes.getName()))) {
return false;
}
return true;
}
public boolean shouldSkipClass(Class aClass) {
return false;
}
};
}
public String fetchUsers(List<User> users, List<String> fields) {
GsonBuilder builder = new GsonBuilder();
builder.setExclusionStrategies(createStrategy(fields));
Gson gson = builder.create();
return gson.toJson(users);
}
public static void main(String[] args) {
ABC x = new ABC();
Address add = x.new Address();
add.city = "city";
add.state = "state";
add.street = "street";
Address[] array = new Address[1];
array[0] = add;
User user = x.new User();
user.address = array;
user.id = "id";
user.name = "name";
List<User> users = new ArrayList<>();
users.add(user);
List<String> fields = Arrays.asList("name", "address", "state");
String json = x.fetchUsers(users, fields);
System.out.println(json);
}
}
and output of this code is :
[{"name":"name","address":[{"state":"state"}]}]
and dependency for Gson is.
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
I'm still new to Java and parsing Json. I'm trying to build a Comic Webapp with Spring. The Database is a Json File, which holds an Array of different Comics.
I wanted to convert the Json Array to Java Objects and put it into an ArrayList but I seem to make a mistake somewhere along the way. Maybe you can tell me what I'm doing wrong? While doing a JUnit Test I get the following error:
Error com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Comic" (class de.uni_koeln.comics.data.Comic), not marked as ignorable (6 known properties: , "title", "issue", "id", "box", "publisher", "comment"])
at [Source: N/A; line: -1, column: -1] (through reference chain: de.uni_koeln.comics.data.Comic["Comic"])
Comic.class
package de.uni_koeln.comics.data;
import de.uni_koeln.comics.service.JsonImportService;
public class Comic {
private JsonImportService jservice;
public String title;
public int id;
public int issue;
public int box;
public String publisher;
public String comment;
public Comic() {
}
public Comic(String title, int issue, int box, String publisher, String comment) {
this.title = title;
this.issue = issue;
this.box = box;
this.comment = comment;
this.publisher = publisher;
}
//setter and getter
`
JsonImportService
package de.uni_koeln.comics.service;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
#Service
public class JsonImportService {
private List<Comic> comicList;
#Test
public void readComicJson() throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(new File("src/main/resources/Comics.json"));
Comic comic = mapper.treeToValue(root, Comic.class);
JsonNode contactNode = root.path("Comic");
if (contactNode.isArray()) {
for (JsonNode node : contactNode) {
String title = node.path("Title").asText();
int issue = node.path("Issue #").asInt();
int box = node.path("Box #").asInt();
String publisher = node.path("Publisher").asText();
String comment = node.path("Comments").asText();
comic.setTitle(title);
comic.setIssue(issue);
comic.setBox(box);
comic.setPublisher(publisher);
comic.setComment(comment);
comicList.add(new Comic(title,box,issue,publisher,comment));
}
}
}
public List<Comic> getComicList() {
return comicList;
}
Try to use the #JsonIgnoreProperties annotation like blow.
#JsonIgnoreProperties(ignoreUnknown = true)
public class Comic {
}
I get the following json from a httpresponse
{
"result": "success",
"team_registration": {
"current_status": "executed",
"expiration_time": "2012-07-18T21:29:43Z",
"id": 609,
"team_id": 50,
}
}
How do I retreive the "result" as a string and the "team_registration" as a POJO (in Android) with Jackson?
Currently I have this:
HttpResponse response = httpClient.execute(httpGet);
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
String json = EntityUtils.toString(entity);
Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
});
result = (String) map.get("result");
resultRegistration = (Registration) map.get("team_registration");
Registration class:
package be.radarwerk.app.model;
import java.io.Serializable;
import java.util.Date;
import org.codehaus.jackson.annotate.JsonIgnore;
public class Registration implements Serializable { // Todo implements parceable?
private static final long serialVersionUID = 1L;
private int id;
private String currentStatus;
private Date expirationTime;
#JsonIgnore
private Volunteer volunteer;
#JsonIgnore
private Team team;
public Registration() {
}
public Registration(int id, String currentStatus, Volunteer volunteer,
Team team) {
super();
this.id = id;
this.currentStatus = currentStatus;
this.volunteer = volunteer;
this.team = team;
}
public int getId() {
return id;
}
public String getCurrentStatus() {
return currentStatus;
}
public Volunteer getVolunteer() {
return volunteer;
}
public Team getTeam() {
return team;
}
public Date getExpirationTime() {
return expirationTime;
}
}
"result" as String works fine but for the "registration_moment" I get this exception:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to Registration
I also tried casting it to a String in the same way as "result" and doing mapper.readValue on that string.
No success.
Any tips?
Your class should be deserialized automatically if you modify it like this (Note! Jackson 2.1+ required):
#JsonIgnoreProperties("team_id")
#JsonNamingStrategy(PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy)
public class Registration implements Serializable
{
private static final long serialVersionUID = 1L;
private int id;
private String currentStatus;
private Date expirationTime;
#JsonIgnore
private Volunteer volunteer;
#JsonIgnore
private Team team;
public Registration() {
}
// other code
}
Then, to deserialize in your code:
Registration registration;
final JsonNode node = mapper.readTree(json);
if (node.get("result").textValue().equals("success"))
registration = mapper.readObject(node.get("team_registration").traverse(),
Registration.class);
Your approach seems a bit odd to me. You should really be using the Android JSONObject class, that's what it's there for. Once you have a JSONObject (or JSONArray), you will need to iterate over it if you want to move elements into a different data structure, but that's very likely unnecessary.
In any event, here's some code (using android-query) to get you to a JSONObject:
String url = "whatever you want";
aq.ajax(url, JSONArray.class, new AjaxCallback<JSONArray>() {
#Override
public void callback(String url, JSONArray json, AjaxStatus status) {
if (json == null) {
Toast.makeText(context, "Failed to retrieve JSON", Toast.LENGTH_SHORT);
}
else {
try {
JSONObject general = json.getJSONObject(0);
...
}
}
}
});