com.apptentive.android.sdk.util.JsonDiffer.java Source code

Java tutorial

Introduction

Here is the source code for com.apptentive.android.sdk.util.JsonDiffer.java

Source

/*
 * Copyright (c) 2014, Apptentive, Inc. All Rights Reserved.
 * Please refer to the LICENSE file for the terms and conditions
 * under which redistribution and use of this file is permitted.
 */

package com.apptentive.android.sdk.util;

import com.apptentive.android.sdk.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.*;

/**
 * @author Sky Kelsey
 */
public class JsonDiffer {

    private JsonDiffer() {
    }

    public static JSONObject getDiff(JSONObject original, JSONObject updated) {
        JSONObject ret = new JSONObject();

        Set<String> originalKeys = getKeys(original);
        Set<String> updatedKeys = getKeys(updated);

        Iterator<String> it = originalKeys.iterator();
        while (it.hasNext()) {
            String key = it.next();
            updatedKeys.remove(key);
            try {
                Object oldValue = original.opt(key);
                Object newValue = updated.opt(key);

                if (isEmpty(oldValue)) {
                    if (!isEmpty(newValue)) {
                        // Old is empty. New is not. Update.
                        ret.put(key, newValue);
                    }
                } else if (isEmpty(newValue)) {
                    // Old is not empty, but new is empty. Clear value.
                    ret.put(key, JSONObject.NULL);
                } else if (oldValue instanceof JSONObject && newValue instanceof JSONObject) {
                    // Diff JSONObjects
                    if (!areObjectsEqual(oldValue, newValue)) {
                        ret.put(key, newValue);
                    }
                } else if (oldValue instanceof JSONArray && newValue instanceof JSONArray) {
                    // Diff JSONArrays
                    // TODO: At least check for strict equality. Right now, we always send nested JSONArrays.
                    ret.put(key, newValue);
                } else if (!oldValue.equals(newValue)) {
                    // Diff primitives
                    ret.put(key, newValue);
                } else if (oldValue.equals(newValue)) {
                    // Do nothing.
                }
            } catch (JSONException e) {
                Log.w("Error diffing object with key %s", e, key);
            } finally {
                it.remove();
            }
        }

        // Finally, add in the keys that were added in the new object.
        it = updatedKeys.iterator();
        while (it.hasNext()) {
            String key = it.next();
            try {
                ret.put(key, updated.get(key));
            } catch (JSONException e) {
                // This can't happen.
            }
        }

        // If there is no difference, return null.
        if (ret.length() == 0) {
            ret = null;
        }
        Log.v("Generated diff: %s", ret);
        return ret;
    }

    public static boolean areObjectsEqual(Object left, Object right) {
        if (left == right)
            return true;
        if (left == null || right == null)
            return false;

        if (left instanceof JSONObject && right instanceof JSONObject) {
            JSONObject leftJSONObject = (JSONObject) left;
            JSONObject rightJSONObject = (JSONObject) right;
            if (leftJSONObject.length() != rightJSONObject.length()) {
                return false;
            }
            Iterator keys = leftJSONObject.keys();
            while (keys.hasNext()) {
                try {
                    String key = (String) keys.next();
                    Object leftValue = leftJSONObject.get(key);
                    Object rightValue = rightJSONObject.get(key);
                    if (!areObjectsEqual(leftValue, rightValue)) {
                        return false;
                    }
                } catch (JSONException e) {
                    Log.w("Error comparing JSONObjects", e);
                    return false;
                }
            }
            return true;
        } else if (left instanceof JSONArray && right instanceof JSONArray) {
            JSONArray leftArray = (JSONArray) left;
            JSONArray rightArray = (JSONArray) right;
            if (leftArray.length() != rightArray.length()) {
                return false;
            }
            try {
                for (int i = 0; i < leftArray.length(); i++) {
                    if (!areObjectsEqual(leftArray.get(i), rightArray.get(i))) {
                        return false;
                    }
                }
            } catch (JSONException e) {
                Log.e("", e);
                return false;
            }
            return true;
        } else if (left instanceof Number && right instanceof Number) {
            // Treat all numbers as doubles. Numbers are equal if within 1/10,000 of each other. This
            // is to account for floating point errors when comparing a float and double of equal value.
            double leftDouble = ((Number) left).doubleValue();
            double rightDouble = ((Number) right).doubleValue();
            double error = Math.abs(0.000001 * rightDouble);
            // Use <= because if both sides are zero, the error will also be zero.
            return Math.abs(leftDouble - rightDouble) <= error;
        } else {
            return left.equals(right);
        }
    }

    private static Set<String> getKeys(JSONObject jsonObject) {
        Set<String> keys = new HashSet<String>();
        if (jsonObject != null) {
            @SuppressWarnings("unchecked")
            Iterator<String> it = (Iterator<String>) jsonObject.keys();
            while (it.hasNext()) {
                keys.add(it.next());
            }
        }
        return keys;
    }

    private static boolean isEmpty(Object value) {
        return value == null || value.equals("");
    }
}