I am needing to parse JSON data coming in from an outside source. The problem is sometimes and array of data is sent in and sometimes it come in as a single object, but the array and the single object have the same name.
{
"OuterObject": {
"Names":[
{
"name": "John Doe"
},
{
"name": "William Watson"
}
]
}
}
But when the JSON array has only one element, it looks like this:
{
"OuterObject": {
"Names": {
"name": "John Doe"
}
}
}
My application needs to be able to handle either one of these, but not both at the same time.
This is what my Json parsed class looks like:
#JsonRootName("OuterObject")
public class OuterObject {
#JsonProperty("Names")
private Names names;
#JsonProperty("Names")
private List<Names> namesList;
public Names getNames() {
return names;
}
public void setNames(Names names) {
this.names = names;
}
public List<Names> getNamesList() {
return namesList;
}
public void setNamesList(List<Names> namesList) {
this.namesList = namesList;
}
}
However, it doesn't look like it will work to have the same json property name for both the list and the single object. It also doesn't appear to just use an array and have the single json object parse into the list. Does anyone know of any ways that my application can handle both json arrays and single json objects when the arrays and the objects have the same name?
You just need to use a single field of type List<Names>, and activate the feature ACCEPT_SINGLE_VALUE_AS_ARRAY
YourClass result = mapper.reader(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.forType(YourClass.class)
.readValue(json);
I have used following method for convert JSONArray, if it is only one JSONObject.
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
private JSONArray getJSONArray(JSONObject json, String field) {
JSONArray array;
if(json.get(field) instanceof JSONObject){
array = new JSONArray();
array.add(json.get(field));
}else{
array = json.getJSONArray(field);
}
return array;
}
Convert your json to Map then use your code to get the desired result.
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.convertValue(json, Map.class);
or better
Map<String, Object> map = mapper.convertValue(json, new TypeReference<Map<String, Object>>() {});
Related
In the Reddit JSON API, comments can contain two different types of JSONArrays, both called "children".
"children" is usually an array of Objects containing a String "kind" and Object "data":
"children": [ { "kind": "t3", "data": {} } ...]
I've been handling these fine. My problem is that, sometimes, children will be a simple String array:
"children": [ "e78i3mq", "e78hees", "e78jq6q" ]
When parsing these, GSON throws an exception like the following:
Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but
was STRING at line 1 column 3780 path
$[1].data.children[0].data.replies.data.children[0].data.replies.data.children[0].data.replies.data.children[0].data.children[0]
How can I handle these String array cases?
If the same endpoint is returning a different type in some instances I suggest wrapping that part in an object and using a deserializer to check the type and assign accordingly. You can do something like this:
public Parent serialize(String jsonString) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(WrappedChild.class, new ChildDeserializer());
Gson gson = builder.create();
return gson.fromJson(jsonString, Parent.class);
}
class Parent {
public List<WrappedChild> children;
}
class ObjectChild {
public String body;
}
class WrappedChild {
public ObjectChild objectChild;
public String stringChild;
}
class ChildDeserializer implements JsonDeserializer<WrappedChild> {
private Gson gson = new Gson();
#Override
public WrappedChild deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json != null) {
if (json.isJsonObject()) {
WrappedChild result = new WrappedChild();
result.objectChild = gson.fromJson(json, ObjectChild.class);
return result;
} else if (json.isJsonPrimitive()) {
WrappedChild result = new WrappedChild();
result.stringChild = json.getAsString();
return result;
}
}
return null; // Or throw new Exception("Unknown child type");
}
}
If you are using retrofit just pass the Gson created by the builder to GsonConverterFactory.create as a parameter when creating your service.
You should carefully study answer from Emre Eran because that way you will have a total control of deserializing. I will just give an another approach which in some cases might require less effort. It bases on the "basic intelligence" of Gson deserializing.
If you declare your class that contains children like:
public class Parent {
Collection<?> children;
}
Gson tries its best to "guess" the object type. If it faces a simple string it will be deserialized to a String. If it faces data like in your 1st Json example, it will deserialized to com.google.gson.internal.LinkedTreeMap which is just a Java version of Json tree.
So depending on how complex is the data object in your 1st example and how you use the result overall you might not need to write custom deserializers (which anyway might be better solution eventually).
Sorry for the late answer, thanks for leading me in the right direction Emre!
I ended up getting GsonBuilder to work with a custom method, getGsonAdaptedData.
After retrieving the JSON response in a background thread:
...
Gson gson = new GsonBuilder().registerTypeAdapter(Data.class, (JsonDeserializer<Data>) (arg0, arg1, arg2) -> {
JsonObject dataJsonObject = arg0.getAsJsonObject();
Data data = new Gson().fromJson(dataJsonObject, Data.class);
return RedditUtils.getGsonAdaptedData(dataJsonObject.get("children").getAsJsonArray(), data);
}).create();
final Feed responseSubredditFeed = gson.fromJson(jsonString, Feed.class);
...
RedditUtils.getGsonAdaptedData
// JSON returned for Reddit comments can contain two types of arrays named "children"
// This method checks to see if we were given a Children array or String array
// JSON member "replies" is similar, and can be found in the Data of some Children
// If the method finds a nested "children" array, it recursively adapts its Data
public static Data getGsonAdaptedData(JsonArray childrenJsonArray, Data data) {
if (childrenJsonArray.size() > 0) {
Gson gson = new Gson();
if (childrenJsonArray.get(0).isJsonObject()) {
data.setChildrenList(gson.fromJson(childrenJsonArray,
new TypeToken<List<Children>>() {
}.getType()));
// Loops through every Data object in the array looking for children and replies
for (int i = 0; i < childrenJsonArray.size(); i++) {
JsonObject nestedDataJsonObject = childrenJsonArray.get(i).getAsJsonObject().get("data").getAsJsonObject();
if (nestedDataJsonObject.has("children")) {
getGsonAdaptedData(nestedDataJsonObject.get("children").getAsJsonArray(), data.getChildren().get(i).getData());
} else if (nestedDataJsonObject.has("replies") && nestedDataJsonObject.get("replies").isJsonObject()) {
data.getChildren().get(i).getData().setRepliesObject(gson.fromJson(nestedDataJsonObject.get("replies"),
new TypeToken<Replies>() {
}.getType()));
getGsonAdaptedData(nestedDataJsonObject.get("replies").getAsJsonObject().get("data").getAsJsonObject().get("children").getAsJsonArray(), data.getChildren().get(i).getData());
}
}
} else {
data.setRepliesList(gson.fromJson(childrenJsonArray,
new TypeToken<List<String>>() {
}.getType()));
}
}
return data;
}
I am trying to parse a json string to java object but i am not sure on the object hierarchy.
below is the json string
{
"TABLE_Detail":{
"1":{
"TABLE":"table1",
"RUN_DATE":"20170313",
"SEQ_NUM":"123",
"START_TIME":"20170313133144",
"END_TIME":"20170313133655"
},
"2":{
"TABLE":"table2",
"RUN_DATE":"20170313",
"SEQ_NUM":"123",
"START_TIME":"20170313133142",
"END_TIME":"20170313133723"
}
}
}
Here the number 1, 2 are dynamic and can go up to any number, I tried to create a outer object and have a Map of type key String and value as object TableData. The map having variable name TABLE_Detail. but the TableData object is always null. TableData object has all the variables.
Please help me on how to convert this json string to object.
Change 1 to table1 and 2 to table2:
public class TableDetails {
private TableData table1;
private TableData table2;
public TableDetails(){
}
// getter and setter
}
And if modify json format to "Koen Van Looveren" mentioned:
public class TableDetails {
List<TableData> tables;
public TableDetails() {
}
// getter and setter
}
The table class:
Table.java:
public class TableData {
private String table;
private String run_date;
private String seq_num;
private String start_time;
private String end_time;
public TableData() {
}
// getter and setter
}
you have two choice for such painfully json structure when using Gson.
using Gson parsing json as Map and write some class access returned Map.this mode works fine for access data only!
//usage
TableDetails details=new TableDetails(gson.fromJson(json, Map.class));
//classes
class TableDetails {
private Map<String, Map> context;
public TableDetails(Map root) {
this.context = (Map<String, Map>) root.get("TABLE_Detail");
}
public int size() {
return context.size();
}
public Table get(String key) {
return new Table(context.get(key));
}
}
class Table {
private Map context;
public Table(Map context) {
this.context = context;
}
public String getName() {
return get("TABLE");
}
private <T> T get(String name) {
return (T) context.get(name);
}
...
}
write your own Gson TypeAdapter,but this way may be more complex.if you interesting on write custom TypeAdapter there is a demo that I written for extract json root.gson-enclosing-plugin
You can try deserialize it into a Map<String, Map<String, TableData>>. The reason why Map<String, TableData> doesn't work it that the pesudo-array is wrapped in another object.
The following example converts a response into a List<TableData>:
public List<TableData> deserialize(String json) {
return Gson().<Map<String, Map<String, TableData>>>fromJson(json, new TypeToken<Map<String, Map<String, TableData>>>(){}.getType())
.values().iterator().next()
.entrySet().stream()
.sorted(Comparator.comparingInt(e -> Integer.parseInt(e.getKey())))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
I was in a search for the solution, and i came across one of the site where the solution worked. i wanted to credit the below site. Thanks for all the support.
I am able to map the dynamic value 1, 2 as map keys and values are mapped correspondingly to the TableData object properties using #SerializedName gson annootation.
http://findnerd.com/list/view/Parse-Json-Object-with-dynamic-keys-using-Gson-/24094/
When using an array in json you need to use [ for opening and ] for closing
{
"TABLE_Detail": [
{
"TABLE": "table1",
"RUN_DATE": "20170313",
"SEQ_NUM": "123",
"START_TIME": "20170313133144",
"END_TIME": "20170313133655"
},
{
"TABLE": "table2",
"RUN_DATE": "20170313",
"SEQ_NUM": "123",
"START_TIME": "20170313133142",
"END_TIME": "20170313133723"
}
]
}
I am trying to parse a JSON object which consists of an Array of Customer objects. Each customer object contains a number of key/value pairs:
{
"Customers":
[
{
"customer.name": "acme corp",
"some_key": "value",
"other_key": "other_value",
"another_key": "another value"
},
{
"customer.name": "bluechip",
"different_key": "value",
"foo_key": "other_value",
"baa": "another value"
}
]
}
The complication is that the keys are not known to me in advance. A second complication is that the keys contain periods (.) that mean that even when I have tried to map them to a field, it fails.
I have been trying to map these to a Customers class:
Customers data = new Gson().fromJson(responseStr, Customers.class);
which looks like this:
public class Customers {
public List<Customer> Customers;
static public class Customer {
public List<KeyValuePair> properties;
public class KeyValuePair {
String key;
Object value;
}
}
}
My problem is that in when I load this class from the JSON, my Customers list populates, but their properties are null. How can I make GSON deal with the fact that I don't know the key names?
I have tried various other approaches including putting a HashMap in the Customer class, in place of the KeyValuePair class.
A different approach is that, you can create a Map of key values from the JSON and then look for the values, since the keys are not known
Gson gson = new Gson();
Type mapType = new TypeToken<Map<String,List<Map<String, String>>>>() {}.getType();
Map<String,List<Map<String, String>> >map = gson.fromJson(responseStr, mapType);
System.out.println(map);
Customers c = new Customers();
c.setCustomers(map.get("Customers"));
System.out.println(c.getCustomers());
Modify your Customers class like this
public class Customers {
public List<Map<String, String>> customers;
public List<Map<String, String>> getCustomers() {
return customers;
}
public void setCustomers(List<Map<String, String>> customers) {
this.customers = customers;
}
}
I have this structure of my JSON response string:
{
"1":{
"data1":"1","data2":"test1", ...
},
"2":{
"data1":"6","data2":"test2", ...
},
...
}
And I want to get the values to put into an ArrayList<MyItem>. I use GSON and normally I can do it in this way:
ArrayList<MyItem> items =
gson.fromJson(jsonString, new TypeToken<ArrayList<MyItem>>() {}.getType());
The problem is, that it does not work, because my JSON String has numbers as keys, but I only want to get the values to put into the ArrayList (unfortunately, the JSON string can not be changed by myself). How can I do this efficiently?
I'd probably deserialize the JSON into a java.util.Map, get the values from the Map as a Collection using the Map.values() method, and then create a new ArrayList using the constructor that takes a Collection.
Write a custom deserializer.
class MyItem
{
String data1;
String data2;
// ...
}
class MyJSONList extends ArrayList<MyItem> {}
class MyDeserializer implements JsonDeserializer<MyJSONList>
{
public MyJSONList deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException
{
MyJSONList list = new MyJSONList();
for (Entry<String, JsonElement> e : je.getAsJsonObject().entrySet())
{
list.add((MyItem)jdc.deserialize(e.getValue(), MyItem.class));
}
return list;
}
}
Example:
String json = "{\"1\":{\"data1\":\"1\",\"data2\":\"test1\"},\"2\":{\"data1\":\"6\",\"data2\":\"test2\"}}";
Gson g = new GsonBuilder()
.registerTypeAdapter(MyJSONList.class, new MyDeserializer())
.create();
MyJSONList l = g.fromJson(json, MyJSONList.class);
for (MyItem i : l)
{
System.out.println(i.data2);
}
Output:
test1test2
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();
}
}
}