How to structure a Map from Json that has duplicate keys - android

Ok so I have this piece of JSON that I want to parse with Gson. I would like the Strings to be the values and the longs to be the keys.
{"completed_questions":[["String",12345],...]}
The issue is the data type, when I try a Map<String, Long> it parses everything but gives me an error because of the duplicate String keys.
I tried to reverse it thinking Gson would know to switch them around but when I tried Map<Long, String> I got an error about not being able to parse my Strings as Longs.
To get it to work I created a swap map class that takes the Key and Value types and swaps them like so public class SwapMap<K, V> implements Map<K, V> however translating the swapped map actions like put/get/remove seem to be pretty difficult to make work.
What's the best way to parse this with Gson even though the strings aren't unique? (But the numbers are)

JSON doesn't allow identical keys on the same level in a json object. It seems like you are trying to map a json array to a java map.
Based on the following data structure, you would need a list if you want to use the default conversion provided by Gson.
{
"completed_questions": [
[
"String",
12345
],
[
"String",
12345
]
]
}
Here is a quick implementation:
private static void mapToObject() {
String json = "{\"completed_questions\":[[\"String\",12345],[\"String\",123456]]}";
Gson gson = new Gson();
CompletedQuestions questions = gson.fromJson(json, CompletedQuestions.class);
for (List<String> arr : questions.getCompleted_questions()) {
for (String val : arr) {
System.out.print(val + " ");
}
System.out.println();
}
}
public static class CompletedQuestions {
List<List<String>> completed_questions;
public List<List<String>> getCompleted_questions() {
return completed_questions;
}
}
This outputs:
String 12345
String 123456
The thing to note is that I am using a list for mapping purposes which closely resembles the data model provided.
This will require you to do the conversion to long yourself. But the way that json string looks. It seems like you would need to operate on the indices. If you have control over the json structure, I would recommending creating a better model. Other wise you can do something like list.get(0) -> your key list.get(1) -> your value which is the long on the inner list.

So what I did is just made a custom Gson Deserializer that mapped these values to a LongSparseArray<String>, which is the best way to go about it.
This is the relevant parts of the Deserializer:
for (JsonElement array : jsonObject.get("my_key").getAsJsonArray()) {
if (array.getAsJsonArray().size() == 2) {
String value = array.getAsJsonArray().get(VALUE).getAsString();
long key = array.getAsJsonArray().get(KEY).getAsLong();
progress.completedActivities.put(key, value);
}
}
Then I just added it to my Gson creator like so:
#Provides #Singleton Gson provideGson() {
return new GsonBuilder()
.registerTypeAdapter(MyClass.class, new MyClass())
.create();
}

Related

Android: Convert LiveData ArrayList to Gson and back again

For my application, I have a recycler view that I'm using LiveData to populate. Each item in the recycler view is an Event object that I created. I'm using Room and Dao to store these Events and to create that abstraction layer between SQL and the repository and UI controller, but the problem is that Dao can only serialize primitize types into JSONs. I created type converters to convert between ArrayList and json, but I need to be able to convert between LiveData[ArrayList[Event]] in order to get this to work.
So far, this is what I have:
#TypeConverter
public static String fromEvent(LiveData<ArrayList<Event>> events){
Gson gson = new Gson();
String json = gson.toJson(events);
return json;
}
#TypeConverter
public static LiveData<ArrayList<Event>> fromEventString (String value){
Type eventType = new TypeToken<LiveData<ArrayList<Event>>>() {}.getType();
return new Gson().fromJson(value, eventType);
}
How do I interconvert between these two data types using Google's Gson library? Lol I'm obviously not too experienced with this.
Thanks for any help!!
This isn't exactly an answer, but I switched from ArrayList to the generic List, and it worked seamlessly. I was reading something about how Gson looks to the superclass of whatever data structure you're using to learn how to serialize and deserialize, so I wondered if switching to: List[Event] list = new ArrayList() instead of ArrayList[Event] list = new ArrayList() would do the trick. Indeed, for some reason RoomDatabase knew perfectly how to serialize it this way.

Best Way to Create Room Database Object

I am New to Room Persistence Library in Android. I have looked into a lot of tutorials related to Room. But something is unclear about it.
I can relate my questions to the below response. If we use #Embedded in "Info", Is it possible to declare info POJO class as separate entity? because inside info I have another object called "preferences". How can I embed that to my Room Database ?
Next is How can I use Awb ArrayList in below sample for Type converters? Note that inside Awb ArrayList has one more "terms" array inside.
I would appreciate if some can show and explain to me the best way to Create Room from the below sample data. I know so many tutorials are there for simple JSON structure, but I didn't find any examples with nested JsonObjects.
{
"hashCode": 10461,
"jobs": [
{
"info": {
"jobDesc": "aaaaa",
"signatureRequired": "true”
"preferences": {
"podRequired": 0,
"eligableForCcod": false,
"signatureRequired": 1
}
},
"awb": [
{
"terms": [
{
"termValue": "INR1500.000",
"termType": "CASH_AMOUNT"
},
{
"termValue": "CTLDk",
"termType": "CX_EN"
}
],
"packagesCount": 1,
"accountId": "AE _MP",
"awb": "1234567878440"
}
]
}
]
}
I don't know if this is the best possible approach, but normally when I have complex objects in Room I define a converter class containing two TypeConverters. The first one converts from the structured object (your POJO class) to a String with GSON library, something like:
#TypeConverter
public String fromPojoToString(MyPojoClass info) {
if (info == null) {
return (null);
}
Gson gson = new Gson();
Type type = new TypeToken<MyPojoClass>() {
}.getType();
return gson.toJson(info, type);
}
The second converter converts from the string (stored inside Room) to the structured object (the POJO class). Something like:
#TypeConverter
public MyPojoClass fromStringtoPojo(String object) {
if (object == null) {
return (null);
}
Gson gson = new Gson();
Type type = new TypeToken<MyPojoClass>() {
}.getType();
return gson.fromJson(info, type);
}
This way you'll be able to serialize/deserialize your POJO class without writing a lot of boilerplate. Obviously, you have to annotate your AppDatabase class (the one that extends RoomDatabase) with your Converter using #TypeConverters annotation.
There is a good article about this approach here https://medium.com/#toddcookevt/android-room-storing-lists-of-objects-766cca57e3f9.
Hope that this helps, best luck!
Explore these link to easy understand room persistance database
https://www.youtube.com/watch?v=KAHAQunQkDE
https://developer.android.com/topic/libraries/architecture/adding-components#room
https://developer.android.com/training/data-storage/room/

Gson converting int into double when parsing json string

I have this JSON file into my assets. I am parsing it using Gson into the following model class:
public class SearchRequest {
private ArrayList<String> _source;
private int from;
private int size;
private Object sort;
private Object query;
public void setFrom(int from) {
this.from = from;
}
public void setSize(int size) {
this.size = size;
}
public void setArtist(String artistName) {
Gson gson = new Gson();
JsonObject object = gson.toJsonTree(query).getAsJsonObject();
JsonArray should = object.getAsJsonObject("function_score").getAsJsonObject("query")
.getAsJsonObject("bool").getAsJsonArray("should");
should.get(0).getAsJsonObject().getAsJsonObject("match").addProperty("album_artist", artistName);
should.get(1).getAsJsonObject().getAsJsonObject("nested").getAsJsonObject("query")
.getAsJsonObject("bool").getAsJsonArray("must").get(0).getAsJsonObject()
.getAsJsonObject("match").addProperty("tracks.artist", artistName);
query = gson.fromJson(object.toString(), query.getClass());
}
}
When I convert this JSON into an object of this class, the query object becomes a LinkedTreeMap. But in this conversion, the key offset which is an integer, becomes double. In my JSON (line number 50), offset is 0, but after conversion, its 0.0. Screenshot:
Why is this happening? How to fix this?
How to fix this?
It's not a subject to be fixed and nothing to be worried about.
Why is this happening?
Your JSON<->Java mapping does not provide any mappings except the top-most one. That makes Gson work like that due to lack of the target type information, and LinkedTreeMap is used internally. Literals like 0 that may look like integers are also legal floating point values from the JSON format point of view: the JSON specification declares numbers only, and does not make any corrections on "integerness". Having no enough information on deserializing the data types, Gson applies the default parsing policies, and chooses java.lang.Double as a type that can hold any other standard numeric values that can hold less significance bits (longs can fit the room of doubles easily; but not sure what Gson does for BigDecimals -- JSON specifications does not seem to make any limits). So this is just internal Gson representation and you have a few options on that:
You can declare a mapping (write or generate by a specialized tool) that would even avoid internal LinkedTreeMaps. Tedious? Maybe. But much more power in type safety, javac control, or your favorite IDE navigation and suggestions.
Gson provides a bunch of methods to convert a JSON tree value to a target type: getAsJsonObject(), getAsJsonArray(), getAsInt(), and more allowing to get the target object in the representation you want. For example,
final Object value = searchRequest.query
.get("function_score").getAsJsonObject()
.get("functions").getAsJsonArray()
.get(0).getAsJsonObject()
.get("linear").getAsJsonObject()
.get("date").getAsJsonObject()
.get("offset").getAsInt();
System.out.println(value + " " + value.getClass());
gives:
0 class java.lang.Integer
because of get("offset").getAsInt() that's internally implemented as return isNumber() ? getAsNumber().intValue() : ...; for JSON primitives.
You can apply partial mappings. For example, you can extract the date JSON tree and convert it to a special mapping having the private int offset; field declared: gson.fromJson(dateJsonObject, DateMapping.class).
Again, Gson just does not have enough type information and works really fine.

JSON tag returns empty array [] while gson expects a string

I am using GSON Library to parse my JSON tag.
some of the tags are expected to hold string values (not arrays). the problem is, sometimes the element is empty [] and when it does that the console gives me this error
expected String but was BEGIIN ARRAY.
The following is the ideal case for my JSON
{
"internet": "600.00",
"internet_remarks": "Fibre 1gbps",
}
but sometimes it becomes
{
"internet": "600.00",
"internet_remarks": [],
}
My parsing code is as follows:
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.create();
MainContainer mainContainer = gson.fromJson(obj, MainContainer.class);
while in my class is as follows the varailbas are defined as follows
private String internet;
private String internet_remarks;
My question is what changes should i make so that the variable accommodate the empty array []
If you don't control the source of the (poorly designed) JSON and can't fix it, then you're going to need to write a custom deserializer that constructs your MainContainer object.
See: How do I write a custom deserializer? here on SO and/or the information in the Gson User's guide
If the only time that field is an array type is when it's an empty array, the easiest approach I can think of is simply inspecting the returned JSON and if it's an array, remove it. Then deserialize to your MainContainer. Gson silently ignores any missing elements in the JSON and internet_remarks will be null in your MainContainer.
class MyDeserialier implements JsonDeserializer<MainContainer>
{
#Override
public MainContainer deserialize(JsonElement je, Type type,
JsonDeserializationContext jdc)
throws JsonParseException
{
JsonObject obj = je.getAsJsonObject();
if (obj.get("internet_remarks").isJsonArray())
{
obj.remove("internet_remarks");
}
return new Gson().fromJson(obj, MainContainer.class):
}
}
If that's not actually the case and that array might not be empty, you'll need to add the logic to deal with that and convert it to a String if that's what you really want.
In your case:
The gson parser is confused when you are asking it to parse an array into string.
So, you can change the internet_remarks into an array of string.
Its always a safe approach to use annonations in the container class.
Extras:
When ever you are working with json, these two tools will be handy.
1.OnlineJsonEditor.
2.JsonGen
I would suggest declare your second attribute as an array. i.e.
private String[] internet_remarks;
When you have a single string, still put it in the array.
eg:
{
"internet": "600.00",
"internet_remarks": ["Fibre 1gbps"]
}
When it's empty,
{
"internet": "600.00",
"internet_remarks": []
}
So your json will always be consistent.
Also, I noted that you're not really making use of the GsonBuilder. You could just create the Gson instance with a constructor than a builder.
Gson gson = new Gson();

Gson to convert JSON to Object that contain HastMap of Objects?

I have a problem that I have no idea about this, can anyone help me:
Ex we have a json:
{
"status":"0",
"result": {
"object1": {
"name":"name1",
"age":"21"
},
"object2": {
"event":"new year",
"date":"date"
},
"object1_1": {
"name":"name2",
"age":"22"
},
"object2_1": {
"event":"birthday",
"date":"date"
}
}
}
you can try convert to object by using jackson json.
http://jackson.codehaus.org/
If you want to deserialize this json to an object that contains a Map (and the map contains litteral values and other maps). Assuming you have a bean similar to :
class MyBean {
int status;
Map<String, Object> result;
}
MyBean myBean = new Gson().fromJson(jsonString, MyBean.class);
It should work with no modification. Note that if the type of status is not a number I'm not sure Gson does the conversion as in the json string the value is quoted, same thing applies to your "age" property.
You can also have a look at Genson library http://code.google.com/p/genson/ it has most Gson features, other ones that no other library provide and has better performances. Have a look at the wiki http://code.google.com/p/genson/wiki/GettingStarted.
EDIT
Are the names really things like object1_1, object2_1 etc? When looking at the structure I imagine that object1 goes with object2 and so long. If you use gson you can write a custom TypeAdapter http://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/TypeAdapter.html.
So you can create a root object similar to
class Response {
int status;
List<MyObject> result;
}
class MyObject {
String name;
int age;
String event;
String date;
}
In the read method of your TypeAdapter you should compose instances of MyObject based on the keys (object1 with object2, object1_1 with object2_1...) or take a similar approach.
If you want more details on how to do that you can also ask on Gson google group.

Categories

Resources