5

I want to parse the below JSON into POJO. I am using jackson to parse the json.

{
  "totalSize": 4,
  "done": true,
  "records": [
    {
      "attributes": {
        "type": "oppor",
        "url": "/service/oppor/456"
      },
      "AccountId": "123",
      "Id": "456",
      "ProposalID": "103"
    }
  ]
}

In the above JSON, the fields "totalSize", "done", "records" and "attributes" are known fields. Whereas, "AccountId", "Id" and "ProposalID" are unknown fields. And in the above JSON, I don't need "attributes" to be part of my bean object.

And here is equivalent bean class for my JSON

public class Result {
    private int totalSize;
    private boolean done;
    private List<Map<String, String>> records;

    public int getTotalSize() {
        return totalSize;
    }

    public void setTotalSize(int totalSize) {
        this.totalSize = totalSize;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    public List<Map<String,String>> getRecords() {
        return records;
    }

    public void setRecords(List<Map<String, String>> records) {
        this.records = records;
    }

}

Hence there are unknown fields in the records element I just used List to get the results element in bean. Here in this Map, I don't want the field "attributes". How can I ignore this while parsing? And below is the exception that I am getting as attributes is not a string element.

com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
 at [Source: [B@66fdec9; line: 1, column: 40] (through reference chain: com.sample.json.Result["records"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
    at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:691)
    at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:46)
    at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringMap(MapDeserializer.java:430)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:312)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:26)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:227)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:204)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:23)
sag
  • 5,333
  • 8
  • 54
  • 91
  • Look at http://stackoverflow.com/a/7272468/492884. Something similar should work. – i_v_harish Aug 25 '15 at 06:38
  • @i_v_harish - The referred answer is for converting a java object into JSON. In such case I can use `@JsonProperty` annotation. But for me I need to get the java object from JSON and in that I want to ignore a JSON field – sag Aug 25 '15 at 07:03

5 Answers5

4

1) Create a Record class object

2) Add @JsonIgnore Annotation on fields you won't

public class Result {
    private int totalSize;
    private boolean done;
    private Record records;

[..]

}


public class Record {
    @JsonIgnore
    private Map<String, String> attributes;
    private int accountID;
    private int id;
    private int approvalID;

[..]

}
biology.info
  • 3,500
  • 2
  • 28
  • 39
  • 1
    accountID, id and approvalID are all unknown fields. Unknown fields in the sense they may occur in json or may not. Or there may some other fields like opporId, proposalId and so on. So I can't declare those elements as fields in my Record class. Thats the reason I am reading the element as map – sag Aug 28 '15 at 03:41
4

UPDATE 2015/08/29:

As you have commented that

I achieved dynamic field support by parsing the JSON into map. Ignoring bad JSON element is what pending

I suggest that you should process original JSONObject to remove the "attributes" element from it.

Original JSONObject, for example:

{
  "totalSize": 4,
  "done": true,
  "records": [
    {
      "attributes": {
        "type": "oppor",
        "url": "/service/oppor/456"
      },
      "AccountId": "123",
      "Id": "456",
      "ProposalID": "103"
    }
  ]
}

After process, new JSONObject will be like the following:

{
    "records": {
        "AccountId": "123",
        "Id": "456",
        "ProposalID": "103"
    },
    "totalSize": 4,
    "done": true
}

Use the code as the following:

        JSONObject jsonObject;
        try {            
            jsonObject = new JSONObject(jsonString1);
            JSONArray jsonArray = new JSONArray(jsonObject.get("records").toString());            
            JSONObject jsonObject1 = jsonArray.getJSONObject(0);
            jsonObject1.remove("attributes");
            jsonObject.put("records", jsonObject1);
        } catch (JSONException e) {
            e.printStackTrace();
        }

Then, use your own code that achieved dynamic field support by parsing the JSON into map.

END OF UPDATE 2015/08/29

I suggest that you use Gson and transient in this case

Like this

        String jsonString1 = "{\n" +
                "  \"totalSize\": 4,\n" +
                "  \"done\": true,\n" +
                "  \"records\": [\n" +
                "    {\n" +
                "      \"attributes\": {\n" +
                "        \"type\": \"oppor\",\n" +
                "        \"url\": \"/service/oppor/456\"\n" +
                "      },\n" +
                "      \"AccountId\": \"123\",\n" +
                "      \"Id\": \"456\",\n" +
                "      \"ProposalID\": \"103\"\n" +
                "    }\n" +
                "  ]\n" +
                "}";

        Gson gson = new Gson();
        Result result1 = gson.fromJson(jsonString1, Result.class);

Your classes, pay attention to transient:

public class Result {
    private int totalSize;
    private boolean done;
    private List<Record> records;
}

public class Record {
    private transient Map<String, String> attributes;
    private int AccountId;
    private int Id;
    private int ProposalID;
}

You will get the result:

enter image description here

P/S: I tested in Android Studio :)

UPDATE:

      String jsonString1 = "{\n" +
                "  \"totalSize\": 4,\n" +
                "  \"done\": true,\n" +
                "  \"records\": [\n" +
                "    {\n" +
                "      \"attributes\": {\n" +
                "        \"type\": \"oppor\",\n" +
                "        \"url\": \"/service/oppor/456\"\n" +
                "      },\n" +
                "      \"AccountId\": \"123\",\n" +
                "      \"Id\": \"456\",\n" +
                "      \"ProposalID\": \"103\"\n" +
                "    }\n" +
                "  ]\n" +
                "}";
        Gson gson = new Gson();
        Object object = gson.fromJson(jsonString1, Object.class);
        Map<String, String> stringMap = (Map<String, String>) object;
        Result myResult = new Result();
        Iterator entries = stringMap.entrySet().iterator();
        while (entries.hasNext()) {
            Map.Entry entry = (Map.Entry) entries.next();
            String key = entry.getKey().toString();
            String value = entry.getValue().toString();
            switch (key) {
                case "totalSize":
                    myResult.totalSize = (int) Double.parseDouble(entry.getValue().toString());
                    break;
                case "done":
                    myResult.done = Boolean.valueOf(entry.getValue().toString());
                    break;
                case "records":
                    try{
                        Object object1 = entry.getValue();
                        List<Object> objectList = (List<Object>) object1;
                        Map<String, Object> stringMap2 = (Map<String, Object>) objectList.get(0);
                        Map<String, String> recordMap = new HashMap<>();
                        Iterator entries2 = stringMap2.entrySet().iterator();
                        while (entries2.hasNext()) {
                            Map.Entry entry2 = (Map.Entry) entries2.next();
                            String key2 = entry2.getKey().toString();
                            String value2 = entry2.getValue().toString();
                            if (!"attributes".equals(key2)) {
                                recordMap.put(key2, value2);
                            }
                            entries2.remove();
                        }
                        myResult.records = recordMap;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
            }
            entries.remove();
        }

Classes:

public class Result {
    private int totalSize;
    private boolean done;
    private Map<String, String> records;        
}

Debug result:

enter image description here

BNK
  • 23,994
  • 8
  • 77
  • 87
  • accountID, id and approvalID are all unknown fields. Unknown fields in the sense they may occur in json or may not. Or there may some other fields like opporId, proposalId and so on. So I can't declare those elements as fields in my Record class. Thats the reason I am reading the element as map – sag Aug 28 '15 at 03:51
  • If, for example, AccountId does not occur in json string, then it will get a default value 0. My code still works. For other fields, IMO, you should use `Object object = gson.fromJson(jsonString1, Object.class);` then parse the result object to get desired values – BNK Aug 28 '15 at 04:32
  • This JSON is valid for me as well `{ "totalSize": 4, "done": true, "records": [ { "attributes": { "type": "oppor", "url": "/service/oppor/456" }, "opporId": "123", "Id_1": "456", "Proposal_ID": "103" } ] }` Instead of reading it as object I want to read it as Map. I just want to ignore the `attributes` field – sag Aug 28 '15 at 04:57
  • I don't understand what your new comment means :) – BNK Aug 28 '15 at 04:59
  • You can see the difference in the json present in above comment. There is no AccountId. And I don't want this field itself to be present in Result class. i.e. not with default value. My requirement is to construct the [dataframe] (http://spark.apache.org/docs/latest/sql-programming-guide.html) using this JSON. Consider dataframe as a table. So if a element is not present in JSON, I just don't want to create a dataframe with default values. – sag Aug 28 '15 at 05:06
  • I'll try to put my question in very simple terms. I have a json and I want to parse the json. But there is bad JSON element called "attributes" in json. So I want to ignore the bad JSON element while parsing. – sag Aug 28 '15 at 05:06
  • I think you have 2 requirements: 1. ignore "attributes", 2. dynamic fields supported. Right? – BNK Aug 28 '15 at 05:22
  • yes. I achieved dynamic field support by parsing the JSON into map. Ignoring bad JSON element is what pending – sag Aug 28 '15 at 05:32
  • transient variable/field does not help you for requirement #1? – BNK Aug 28 '15 at 05:35
  • If I have the transient variable, then how other fields will be read. Result itself an Map due to dynamic fields. But in that map, I want to ignore a field – sag Aug 28 '15 at 06:05
  • I have updated my answer. Although not beautiful code and need more tuning, however, at least meet your requirement, I hope :) – BNK Aug 28 '15 at 07:03
  • Thanks for the update. But it will throw class classCastException as attributes is not a string element. So converting into Map will result in error. But this is the code that I exactly have it now. But I don't want to iterate every record to "attributes" element. Instead I just don't want to read it – sag Aug 28 '15 at 07:13
  • Casting Exception can be easily fixed, I believe (my jsonString1 copied from your question). About my answer, I have tried my best, sorry if it does not fixed your issue. Goodluck with other better answers :) – BNK Aug 28 '15 at 07:26
1

Create a new POJO class for attributes,

public class Result {
    private int totalSize;
    private boolean done;
    private List<Attributes> records;

    // Your Getters & Setters
}

public class Attributes{
    List<Map<String,String>> attributes;
    // Add other variables if necessary like AccountId, etc.,

    // Your Getters & Setters
}
Ajeesh
  • 1,572
  • 3
  • 19
  • 32
  • I don't need "attributes" to be part of my bean object – sag Aug 25 '15 at 05:34
  • @SamuelAlexander you mean you don't need attributes variable or Attributes class? – Ajeesh Aug 25 '15 at 05:35
  • I don't need the field "attributes" to be read as POJO. Basically from the above JSON I need "totalSize", "done" and "records". In the "records" I need all elements present in the JSON except "attributes" – sag Aug 25 '15 at 05:42
  • @SamuelAlexander Then try to use **private Object records;** instead of **private List records;** and try to extract values from that object by casting accordingly. I am not sure this works properly or whether casting will have any issues. This is just one suggestion. – Ajeesh Aug 25 '15 at 07:10
  • Thanks a lot for suggestion. Infact my current is like that. I am using object and ignoring "attributes" while reading the fields in Map. But if we ignore the "attributes" while parsing itself, the code will be much better – sag Aug 25 '15 at 07:16
1

I would suggest to use [Google gson API][1]'s @Expose annotation. (if that is allowed in your environment).

You can simply annotate the fields(with @Expose) which are required in your generated json file, and leave it other fields. And during generating json, use API method, excludeFieldsWithoutExposeAnnotation.

Sample example can be seen here.

Note : In your example, treat your Result as Main POJO, and records is another POJO which has attributes,accountId etc fields. Then there is has-a relationship (Java composition) between them.

And after that, you can invoke Json to pojo conversion like below--

com.google.gson.Gson gson = new com.google.gson.GsonBuilder()
                    .excludeFieldsWithoutExposeAnnotation().create();

 Result result= gson.fromJson(yourjsonString, Result.class);
Community
  • 1
  • 1
Ashish Patil
  • 4,428
  • 1
  • 15
  • 36
  • The fields accountId, Id... are dynamic fields. It may present in JSON or may not present in JSON. And the results might have different fields like opprId, prop_id and so on. Only attributes field is static field and I want to ignore that. In that case how I need to define my Result class? – sag Aug 28 '15 at 07:46
0

If you have specific fields you don't want to map, u can use @JsonIgnore annotation over the field name

public class MyJsonObj{
@JsonProperty("name")
String fieldName    

@JsonIgnore
String fieldNameIgnored;
}

If you want to ignore all the fields not mentioned in your POJO, you can use @JsonIgnoreProperties annotation over class

@JsonIgnoreProperties(ignoreUnknown = true)
public class MyJsonObj{
strong text
}
Pratap Singh
  • 4,607
  • 1
  • 22
  • 24