0

For learning purposes i'm creating an android app by using Realm and the Edinburg Festival Api. It's going pretty well except for one problem.

I'm using the following to convert the retrieved JSON to RealmObjects:

public void onResponse(final String response) {
    realm.executeTransactionAsync(new Realm.Transaction(){
        @Override
        public void execute(Realm realm) {
            // Update our realm with the results
            parseImages();
            realm.createOrUpdateAllFromJson(Festival.class, response);
        }
    }
}

This works fine except for one field, the images. The image part of the JSON:

"images": {    
    "031da8b4bad1360eddea87e8820615016878b183": {
        "hash": "031da8b4bad1360eddea87e8820615016878b183",
        "orientation": "landscape",
        "type": "hero",
        "versions": {
            "large-1024": {
            "height": 213,
            "mime": "image/png",
            "type": "large-1024",
        }
        "width": 1024
    }
}

The problem here is the hash inside the image object. I have no clue how to handle this. The hash is different for every festival. Would it be possible to to make a custom JSON deserializer in my RealmObject?

Last code sample is my current model:

public class Festival extends RealmObject {
    @PrimaryKey
    public String title;
    RealmList<Image> images;
    public String description_teaser;
    public String description;
    public String genre;
    public String age_category;
    public String website;
    public RealmList<Performance> performances;
    public int votes;
}

I'm aware my PK is not optimal but this is still just testing to get the images working and i needed to set a PK for migrating.

Any tips are welcome, cheers :)

Update

Added the image model:

public class Image extends RealmObject {
    public String hash;
    public String orientation;
    public String type;
    RealmList<Version> versions;
}

Update 2

My attempt to parse the images before calling realm.createOrUpdateAllFromJson(Festival.class, response);

private void parseImages(String jsonString) throws JSONException {
    JSONArray jsonArr = new JSONArray(jsonString);
    for(int i = 0; i < jsonArr.length(); i++){
        JSONObject jsonObj = jsonArr.getJSONObject(i);
        JSONObject images = (JSONObject)jsonObj.get("images");
        Iterator<String> iter = images.keys();
        while (iter.hasNext()) {
            String key = iter.next();
            try {
                JSONObject value = json.get(key);
                realm.createOrUpdateObjectFromJson(Image.class,value);
            } catch (JSONException e) {
                // Something went wrong!
            }
        }
    }
}

Update 3

I created a function that cleans up the broken JSON i get from the API. It ain't very nice but it works for now. it removes the hashes and the wierd versions and just places them both in a array. I'm sure it could be more efficiently written but i'll just go with this so i can move on with the rest of my app for now. See my own answer.

Juxture
  • 253
  • 3
  • 15

2 Answers2

1

my own temporary solution:

    /**
     * Function to fix the json coming from the Festival API
     * This is a bit more complicated then it needs to be but realm does not yet support @Serializedname
     * It removes the "large-1024" (and simllar) object and places the versions in a JSON version array
     * Then it removes the hashes and creates and images array. The JsonArray can now be parsed normally :)
     *
     * @param jsonString Result string from the festival api
     * @return JSONArray The fixed JSON in the form of a JSONArray
     * @throws JSONException
     */
    private JSONArray cleanUpJson(String jsonString) throws JSONException {
        JSONArray json = new JSONArray(jsonString);
        for(int i = 0; i < json.length(); i++){
            // We store the json Image Objects in here so we can remove the hashes
            Map<String,JSONObject> images = new HashMap<>();
            JSONObject festivalJson = json.getJSONObject(i);
            JSONObject imagesJson = (JSONObject)festivalJson.get("images");
            // Iterate each hash inside the images
            Iterator<String> hashIter = imagesJson.keys();
            while (hashIter.hasNext()) {
                String key = hashIter.next();
                try {
                    final JSONObject image = imagesJson.getJSONObject(key);

                    // Remove the version parents and map them to version
                    Map<String, JSONObject> versions = new HashMap<>();
                    JSONObject versionsJsonObject = image.getJSONObject("versions");

                    // Now iterate all the possible version and map add to the hashmap
                    Iterator<String> versionIter = versionsJsonObject.keys();
                    while(versionIter.hasNext()){
                        String currentVersion = versionIter.next();
                        versions.put(currentVersion,versionsJsonObject.getJSONObject(currentVersion));
                    }

                    // Use the hashmap to modify the json so we get an array of version
                    // This can't be done in the iterator because you will get concurrent error
                    image.remove("versions");
                    Iterator hashMapIter = versions.entrySet().iterator();
                    JSONArray versionJsonArray = new JSONArray();
                    while( hashMapIter.hasNext() ){
                        Map.Entry pair = (Map.Entry)hashMapIter.next();
                        versionJsonArray.put(pair.getValue());
                    }
                    image.put("versions",versionJsonArray);
                    Log.d(LOG_TAG,image.toString());
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                images.put(key,imagesJson.getJSONObject(key));
            }
            // Now let's get rid of the hashes
            Iterator hashMapIter = images.entrySet().iterator();
            JSONArray imagesJsonArray = new JSONArray();
            while( hashMapIter.hasNext() ){
                Map.Entry pair = (Map.Entry)hashMapIter.next();
                imagesJsonArray.put(pair.getValue());
            }
            festivalJson.put("images", imagesJsonArray);
        }
        return json;
    }

Hope it helps someone :) But sure ain't neat.

Juxture
  • 253
  • 3
  • 15
0

Due to how the keys are dynamic in this JSON (why isn't this an array? Whoever designed this API had no idea what they were doing), you'll have to manually parse the object up to the point of the hash key:

JSONObject jsonObj = new JSONObject(jsonString);
JSONObject images = (JSONObject)jsonObj.get("images");
Iterator<String> iter = images.keys();
while (iter.hasNext()) {
    String key = iter.next();
    try {
        JSONObject value = json.get(key);
        realm.createOrUpdateObjectFromJson(Image.class, value.toString());
    } catch (JSONException e) {
        // Something went wrong!
    }
}
Community
  • 1
  • 1
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • Thanks for your answer. How would i combine this with the current way i parse the json? Should i @ignore images and then run this code after to add the images? Or can i somehow implement this inside the realm.createOrUpdateAllFromJson? – Juxture Nov 11 '16 at 08:21
  • You would need to run this code before feeding it to `createOrUpdateAllFromJson` – Christian Melchior Nov 11 '16 at 08:25
  • Each image object should be stored separately as an object while parsing the images one-by-one by key. The hash itself can also be found in the object, after all, so you lose no information. `createOrUpdateAll` expects an array, but this is not an array. – EpicPandaForce Nov 11 '16 at 08:42
  • Sorry to bother you again, i'm not getting this to work. The first object in the response is an array of festivals. I tried to iterate over the array and then use your code. I've uploaded the full JSON to clarify it: https://gist.github.com/RobAben/878c2577738e54ee61310586e95da335 – Juxture Nov 11 '16 at 09:29
  • Well that json is a slight bit different from what was presented -_- – EpicPandaForce Nov 11 '16 at 09:42
  • Sorry for the miscommunication on my end :(. I now got it working up to the Version object. Is there any way to map the "large-1024" etc to a single object? Since the values inside these are all the same. – Juxture Nov 11 '16 at 10:10
  • What kinda sucks is that while I handled the hash, the version has the same problem, which would mean you can't just directly write the JSON to Realm either. What an ugly API. I'll have to edit my answer to provide a complete answer, the edit won't happen ASAP though. Please remove the "correct answer" tick until then. – EpicPandaForce Nov 11 '16 at 10:15