I have multiple JSON files, but they all look similar to this one (some much longer):
{
"$id": "http://example.com/myURI.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"description": "Sample Procedure schema.",
"properties": {
"prop1": {
"description": "",
"type": "string"
},
"prop2": {
"description": "",
"type": "number"
}
"prop3": {
"description": "",
"type": "string"
}
}
}
I want to extract the names (ex. "prop1","prop2") along with their types (ex. "string,"number") and format it to look something like the following:
public static class Whatever {
#JsonProperty
public String prop1;
#JsonProperty
public Integer prop2;
#JsonProperty
public String prop3;
public Whatever() {}
public Whatever(String prop1, Integer prop2, String prop3){
this(prop1, prop2, prop3);
}
}
I've never used Java before so I'm not sure where to begin in creating this script. Mainly I'm concerned with how I will iterate through the json object.
I would Approach this this way:
Create a main class that holds all the information of the JSON and a Secondary class that keeps the information of the prop properties of the JSON:
public class MainJsonObject
{
private int $id;
private int $schema;
// All the other objects that you find relevant...
private List<SecondaryJsonObject> propsList = new ArrayList<>(); // This will keep all your other props with description and type
// Getter and setter, do not forget them as they are needed by the JSON library!!
}
public class SecondaryJsonObject
{
private String description;
private String type;
// Getter and setter, do not forget them !!
}
You could iterate through your JSON Object this way:
First of all include the JSON Library in your project.
Then iterate through your JSON like this:
JSONObject jsonObject = new JSONObject(jsonString);
MainJsonObject mjo = new MainJsonObject();
mjo.set$id(jsonObject.getInt("$id")); // Do this for all other "normal" attributes
// Then we start with your properties array and iterate through it this way:
JSONArray jsonArrayWithProps = jsonObject.getJSONArray("properties");
for(int i = 0 ; i < jsonArrayWithProps.length(); i++)
{
JSONObject propJsonObject = jsonArrayWithProps.getJSONObject(i); // We get the prop object
SecondaryJsonObject sjo = new SecondaryJsonObject();
sjo.setDescription(propJsonObject.getString("description"));
sjo.setTyoe(propJsonObject.getString("type"));
mjo.getPropsList().add(sjo); // We fill our main object's list.
}
Related
I am new to deserialization and I am trying to deserialize the complex JSON for my class. I am using Jackson 2.12.1. I wanted to know how to map the following JSON structure to my class when there are multiple cases that map to the different root element
I have a JSON file in my Resources folder of the classpath and its something like this:
{
"Employee":{
"firstName":"First Name",
"lastName":"Last Name",
"department":"Department"
},
"Student":{
"firstName":"Student First Name",
"lastName":"Student Last Name",
"studentID":"1234"
}
}
I have 2 class for Employee and Car separately which extends the Person abstract class and its something like this:
#Getter
#Setter
public abstract class person{
private String firstName;
private String lastName;
}
#Getter
#Setter
#JsonRootName(value = "Employee")
public Employee extends person{
private String department;
}
#Getter
#Setter
#JsonRootName(value = "Student")
public Student extends person{
private String studentID;
}
public class MainClass{
public static void main(String []args){
String jsonFile = FileUtils.readFileToString(new File("src/main/resources/myFile.json"), "UTF-8");
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
Employee eventInfo = objectMapper.readValue(jsonFile, Employee.class);
//This works for Employee but I want to make sure that it works for Student as well based on the ROOT ELEMENT value within the JSON
}
}
This works for Employee but how can I configure it to work for all the type based on the different ROOT ELEMENT values within the JSON? I feel I am missing some basic thing can someone please help me?
PS: I am using the #Getter and #Setter from the Project Lombok
I tried to look at many examples and the documentation of Jackson. I was finally able to get it working. This is just an example that can be used as a basis for mapping the class. In my case I was trying to see what type of element was present based on which I was mapping to different classes. So this may not work exactly as you expect but still be good for reference.
I am posting the same here so it can be helpful to someone in the future:
If following is the JSON file content:
[
{
"isA": "Type1",
"name": "Test",
"foo": "val1",
"foo": "val2",
"bar": "val3",
"foo": {
"myField": "Value1",
"myField": "value2"
}
},
{
"isA": "Type2",
"name": "Test1",
"foo": "val1",
"foo": "val2",
"bar": "val3",
"foo": {
"myField": "Value1",
"myField": "value2"
}
}
]
public void xmlConverter(InputStream jsonStream) throws IOException {
// Get the JSON Factory and parser Object
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);
// Check the first element is ARRAY if not then invalid JSON throw error
if (jsonParser.nextToken() != JsonToken.START_ARRAY) {
throw new IllegalStateException("Expected content to be an array");
}
jsonParser.nextToken();
// Loop until the end of the events file
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
// Get the node
final JsonNode jsonNode = jsonParser.readValueAsTree();
//Check if the JsonNode is valid if not then exit the process
if (jsonNode == null || jsonNode.isNull()) {
System.out.println("End Of File");
break;
}
// Get the eventType
final String eventType = jsonNode.get("isA").asText();
// Based on eventType call different type of class
switch (eventType) {
case "Type1":
final Type1 type1Info = objectMapper.treeToValue(jsonNode, Type1.class);
break;
case "Type2":
final Type2 type2Info = objectMapper.treeToValue(jsonNode, Type2.class);
break;
default:
System.out.println("JSON event does not match any of event : " + eventType);
break;
}
}
}
I'm using Retrofit 2 in combination with Gson and RxJava. My JSON data looks something like this:
{
"groups": {
"1": {
"name": "First group",
"type": "some data",
// ... more data
},
"2": {
"name": "Second group",
"type": "some data",
// ... more data
},
"3": {
"name": "Third group",
"type": "some data",
// ... more data
}
// 4, 5, 6, etc...
},
// ... more data
}
In the example above the "keys" 1, 2, 3 are integers but they can also be unique strings. I want to map this JSON data to something like this:
public class MyData {
#SerializedName("groups")
private Map<String, Group> mGroups;
// ... more data
}
public class Group {
// TODO: This should be "1", "2", "3", etc.
#SerializedName(???)
private String mId;
// This should be "First group", "Second group", "Third group", etc.
#SerializedName("name")
private String mName;
// This should be "some data"
#SerializedName("type")
private String mType;
// ... more data
}
What's the best way to put the dynamic key (1, 2, 3) in the Group object as well? I'm not hosting the JSON data myself so it cannot be changed to another format.
Step A - Create a group class:
public class Group {
String name;
String type;
}
Step B - Create a groups class:
public class Groups {
List<Group> userList;
}
Step C - Create a GSON Deserializer class
public class MyDeserializer implements JsonDeserializer<Groups> {
private final String groups_key = "groups";
#Override
public Groups deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Groups groups = new Groups();
JsonObject object = json.getAsJsonObject().getAsJsonObject(groups_key);
Map<String, Group> retMap = new Gson().fromJson(object, new TypeToken<HashMap<String, Group>>() {}.getType());
List<Group> list = new ArrayList<Group>(retMap.values());
groups.userList = list;
return groups;
}
}
Step D - Register your Deserializer when you create your Gson object
Gson gson = new GsonBuilder()
.registerTypeAdapter(Groups.class, new MyDeserializer()).create();
Step E - Convert your JSON object via. Gson
Groups groups = gson.fromJson(jsonExmple, Groups.class);
Notes:
When your JSON object gets bigger you can expand your Group/Groups
classes and add more variables. Keep in mind that you will need to reflect everything in your Deserializer class. :)
Use #SerializedName annotation when your variable got a different name from your JSON key
I've JSON structure like follows -
{
"status": true,
"message": "Registration Complete.",
"data": {
"user": {
"username": "user88",
"email": "user#domain.com",
"created_on": "1426171225",
"last_login": null,
"active": "1",
"first_name": "User",
"last_name": "",
"company": null,
"phone": null,
"sign_up_mode": "GOOGLE_PLUS"
}
}
}
Above format is common . Only data key can hold different types of information like user, product, invoice etc.
I want to keep status, message and data keys same in every rest response. data will be treated according to status and message will be displayed to user.
So basically, above format is desired in all apis. Only information inside data key will be different each time.
And I've setup a following class and set it up as gson converter - MyResponse.java
public class MyResponse<T> implements Serializable{
private boolean status ;
private String message ;
private T data;
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
Deserializer.java
class Deserializer<T> implements JsonDeserializer<T>{
#Override
public T deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException{
JsonElement content = je.getAsJsonObject();
// Deserialize it. You use a new instance of Gson to avoid infinite recursion to this deserializer
return new Gson().fromJson(content, type);
}
}
And used it as follows -
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
gsonBuilder.registerTypeAdapter(MyResponse.class, new Deserializer<MyResponse>());
...... ..... ....
restBuilder.setConverter(new GsonConverter(gsonBuilder.create()));
Service interface is as follows -
#POST("/register")
public void test1(#Body MeUser meUser, Callback<MyResponse<MeUser>> apiResponseCallback);
#POST("/other")
public void test2(Callback<MyResponse<Product>> apiResponseCallback);
Problem
I can access status and message fields from inside callback. But information inside data key is not parsed and model like MeUser and Product always returns as empty.
If I change json structure to following above code works perfectly -
{
"status": true,
"message": "Registration Complete.",
"data": {
"username": "user88",
"email": "user#domain.com",
"created_on": "1426171225",
"last_login": null,
"active": "1",
"first_name": "User",
"last_name": "",
"company": null,
"phone": null,
"sign_up_mode": "GOOGLE_PLUS"
}
}
How can I have it worked with specifying separate key inside data object and parse it successfully ?
If I can suggest to change something in json is that you have to add at one new field that defines the type of data, so json should look like below:
{
"status": true,
"message": "Registration Complete.",
"dataType" : "user",
"data": {
"username": "user88",
"email": "user#domain.com",
"created_on": "1426171225",
"last_login": null,
"active": "1",
"first_name": "User",
"last_name": "",
"company": null,
"phone": null,
"sign_up_mode": "GOOGLE_PLUS"
}
}
The MyResponse class has to have new filed DataType so it should look like below:
public class MyResponse<T> implements Serializable{
private boolean status ;
private String message ;
private DataType dataType ;
private T data;
public DataType getDataType() {
return dataType;
}
//... other getters and setters
}
The DataType is an enum which defines type of data. You have to pass Data.class as param in constructor. For all data types you have to create new classes. DataType enum should look like below:
public enum DataType {
#SerializedName("user")
USER(MeUser.class),
#SerializedName("product")
Product(Product.class),
//other types in the same way, the important think is that
//the SerializedName value should be the same as dataType value from json
;
Type type;
DataType(Type type) {
this.type = type;
}
public Type getType(){
return type;
}
}
The desarializator for Json should looks like below:
public class DeserializerJson implements JsonDeserializer<MyResponse> {
#Override
public MyResponse deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
JsonObject content = je.getAsJsonObject();
MyResponse message = new Gson().fromJson(je, type);
JsonElement data = content.get("data");
message.setData(new Gson().fromJson(data, message.getDataType().getType()));
return message;
}
}
And when you create RestAdapter, in the line where you register Deserializator, you should use this :
.registerTypeAdapter(MyResponse.class, new DeserializerJson())
Other classes (types of data) you define like standard POJO for Gson in separated classes.
Your issue is because the data attribute is defined as T which you expect to be of types MeUser, Product, etc, but is actually of an object which has inner attribute like user. To resolve this, you need to introduce another level of classes which has the required attributes user, product, invoice etc. This can be easily achieved using static inner classes.
public class MeUser{
private User user;
public static class User{
private String username;
//add other attributes of the User class
}
}
Might be a little bit off-topic, but what happens if the inner object contains a Date property? My TypeAdapter looks like this:
.registerTypeAdapter(Date.class, new DateDeserializer())
.registerTypeAdapter(GenericNotificationResponse.class, new NotificationDeserializer())
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.create();
But due to the parsing which is done by this : message.setData(new Gson().fromJson(data, message.getDataType().getType()));
It will throw an error whenever it will try to deserialize the Date property. Is there a quick fix for this?
EDIT: Marked the answer as accepted, definitely :) it helped me fix my issue. But now there's this problem with the date deserializer.
I have the following Item class:
public class Item {
public Object item;
}
I am inserting a JSON into this object using GSON.
tmp =
{
"_id": {
"$oid": "5076371389d22e8906000000"
},
"item": {
"values": [
{
"value1": [
4958,
3787,
344
],
"value2": [
4,
13,
23
]
}
],
"name": "item1"
}
}
Java bit:
Item item = new Item();
Gson g = new Gson();
it = g.fromJson(tmp.toString(), Item.class);
it.item becomes a StringMap type (http://code.google.com/p/google-gson/source/browse/trunk/gson/src/main/java/com/google/gson/internal/StringMap.java?r=1131)
I now need to access the sub-objects within this object.
I can use the overridden toString function this type has which prints all objects within this object. But how would I be able to navigate through it?
P.S. The reason I put everything into an object datatype not a structured class is that the JSON structure varies each time, so I can't really have a class schema.
Any suggestions?
You should create an object structure that reflects the JSON instead (since this is what you're trying to do anyway). For your example, you could use this:
public class MyObject {
private Item item;
private String _id;
// getters, setters, etc.
}
public class Item {
private List<Value> values;
private String name;
// getters, setters, etc.
}
public class Value {
private List<Integer> values1;
private List<Integer> values2;
// getters, setters, etc.
}
Then pass MyObject.class to Gson:
MyObject myObj = g.fromJson(tmp.toString(), MyObject.class);
You can get the lists in values like so:
List<Integer> values1 = myObj.getItem().getValues().get(0).getValues1();
List<Integer> values2 = myObj.getItem().getValues().get(0).getValues2();
Try that and see if it works.
Also, you should check out my answer to a similar question here, specifically the part at the end about how to write an object structure for Gson based on some JSON object.
You can always create a constructor for the custom object that uses reflection and takes the StringMap
public MyObject(StringMap sm){
Iterator it = sm.entrySet().iterator();
while(it.hasNext()){
Entry pairs = (Entry)it.next();
Class<?> c = this.getClass();
try {
Field value = c.getDeclaredField((String) pairs.getKey());
value.set(this, pairs.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}
I have a json string like this:
{
"d": {
"results": [
{
"__metadata": {
"uri": "http://localhost:2000",
"key_fields": "Accountnum",
"rows_affected": 0,
"last_autoinc": 0
},
"Accountnum": "9999999",
"workphone": null,
"name": "Smith",
"address": "33 Main St",
"city": "Anytown",
"state": "FL",
"zip": "33333",
}
]
}
}
and I tried to deserialize it according to diffrent questions here on stackoverflow, but I can't get it right.
Here is what I did I created a class, I only need the accountnum and name.
public class Result {
#SerializedName("Accountnum")
public String accountnumStr;
#SerializedName("name")
public String nameStr;
}
I have a string with the json myresult.
Gson gson = new Gson();
Result result = gson.fromJson(myresult,Result.class);
myName.setText(result.nameStr);
I receive an empty string.
Thanks
Since there is a object holding the result object your trying to create, you have make the result class an inner class. You're Result class would have to look like this:
public class ResultParent {
public class Result {
#SerializedName("Accountnum")
public String accountnumStr;
#SerializedName("name")
public String nameStr;
}
}
If you visualize your JSON in terms of class objects then it will be no problem at all. Imagine that your root object contains a property named "d" of type "D". And type "D" contains a list of "results" of another type "Result". The result contains its properties as above. Here's the picture of it:
class RootObj {
D d;
}
class D {
List<Result> results;
}
class Result {
#SerializedName("Accountnum")
public String accountnumStr;
#SerializedName("name")
public String nameStr;
}
To get the objects from a JSON, you would do the following:
Gson g = new Gson();
RootObj ro = g.fromJson(jsonString, RootObj.class);