Serializing (de-serializing) object to (from) JSON string on Android OS

Hi guys and girls,

During my last project on Android I had a task of serializing / de-serializing an object to / from text file. I could choose either XML or JSON formats, so I couldn’t use ObjectInputStream and ObjectOutputStream pair. It was obvious to me that the Android framework should contain something that can do such a generic task. However, after digging some more I’ve discovered that nothing similar exists. Regular Java SDK has the XMLEncoder class that could do the job, but it was not ported to Android. Consequently I had no choice, but to write one myself. I chose JSON format since we have server infrastructure that talks JSON and since Android framework contains a handy class called JSONObject that can parse JSON to / from dictionary object. The purpose of this post is to share this code and some explanation on how it works with you. Please find the code below. You are welcome to use it in your own projects.

Few words on how it works:

JSONObjectInputStream receives JSON data as an InputStream or a String in the constructor. It provides readObject method for retrieving the actual object. It is templated and receives class type and optionally initial object instance of this type. It then traverses the class fields using reflection and searches for the value in the parsed JSON data. If values are found they are overridden in the instance. If not, the existing values are kept.

JSONObjectOutputStream is simpler . It receives OutputStream in the constructor. Its writeObject method traverses the class fields using reflection and writes the values in the JSONObject dictionary. Then JSONObject is converted to string and written to the received OutputStream

Some of the known limitations of the presented code: it skips static fields, but this is very easy to fix (just remove the if-clause). It doesn’t work on non-flat classes (only classes that contain primitives and strings). This functionality can be added on demand as well.

You can find all the code, including Android demo project that uses it here

JSONInputStream code:

 package com.JSONObjectSerialization;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;


/**
* @author dimast
* 
* This class can deserialize classes from JSON strings.
* 
* This works for classes that contain only primitive fields.
* 
*/
public class JSONInputStream extends InputStream 
{
/**
* Creates the object.
* 
* @param is Input stream that contains the JSON content
*/
    public JSONInputStream(final InputStream is)
    {
        _is = is;
        _json = null;
    }

/**
* Creates the object.
* 
* @param json Input string that contains the JSON content
*/
    public JSONInputStream(final String json)
    {
        _json = json;
        _is = null;
    }

/**
* Creates the object from the stream content. 
* @param <T> The type of the object 
* @param objClass The class of the object
* @throws JSONStreamException in case parsing error is occurred
* @return the instance of the object created from the stream content
*/
    public final <T> T readObject(final Class<T> objClass) throws JSONStreamException
    {
        return readObject(objClass, null);        
    }

/**
* Creates the object from the stream content. 
* @param <T> The type of the object 
* @param objClass The class of the object
* @param initialObj Initial object version
* @throws JSONStreamException in case parsing error is occurred
* @return the instance of the object created from the stream content
*/
    public final <T> T readObject(final Class<T> objClass, final T initialObj) throws JSONStreamException
    {
        if (_is == null && _json == null)
        {
            throw new JSONStreamException("Can't read object, the input is null.");
        }

        if (_json == null)
        {
            try 
            {
                // the below uses platform default char set which is UTF-8
                // https://developer.android.com/reference/java/nio/charset/Charset.html
                _json = readStreamTillEnd(_is, null);
            } 
            catch (Exception e) 
            {
                throw new JSONStreamException("Can't read input stream.", e);
            }
        }

        JSONObject jsonObj = null;
        try 
        {
            jsonObj = new JSONObject(_json);
        } 
        catch (JSONException e) 
        {
            throw new JSONStreamException("Can't deserialize the object. Failed to create JSON object.", e);
        }

        T object = null;
        if (initialObj != null)
        {
            object = initialObj;
        }
        else
        {
            try 
            {
                object = objClass.newInstance();
            } 
            catch (InstantiationException e) 
            {
                throw new JSONStreamException("Can't deserialize the object. Failed to instantiate object.", e);
            } 
            catch (IllegalAccessException e) 
            {
                throw new JSONStreamException("Can't deserialize the object. Failed to instantiate object.", e);
            }
        }        

        Field[] fields = objClass.getDeclaredFields();
        for (Field field : fields)
        {
            if (Modifier.isStatic(field.getModifiers()))
            {
                // skip static
                continue;
            }

            try 
            {
                if (jsonObj.has(field.getName()))
                {
                    Log.i("JSONInputStream", 
                            String.format("Updating field [%s]", field.getName()));
                    field.setAccessible(true);
                    field.set(object, jsonObj.get(field.getName()));
                }
                else
                {
                    Log.i("JSONInputStream", 
                            String.format("Field [%s] doesn't exist. Keeping default value.", field.getName()));
                }
            } 
            catch (IllegalArgumentException e) 
            {
                Log.e("JSONInputStream", 
                        String.format("Failed to get field [%s] %s", field.getName(), e.toString()));
                continue;
            } 
            catch (JSONException e) 
            {
                Log.e("JSONInputStream", 
                        String.format("Failed to get field [%s] %s", field.getName(), e.toString()));
                continue;
            } 
            catch (IllegalAccessException e) 
            {
                Log.e("JSONInputStream", 
                        String.format("Failed to get field [%s] %s", field.getName(), e.toString()));
                continue;
            }
        }

        return object;
    }

    @Override
    @Deprecated
    public final int read() throws IOException 
    {
        return 0;
    }

/**
* Reads the input stream until and and produces String.
* @param is the input stream to read from
* @param enc the encoding, in case of null default encoding is used
* @return the string that represents stream's content
* @throws Exception on error
*/
    private static String readStreamTillEnd(final InputStream is, final String enc) throws Exception
    {
        StringBuilder sb = new StringBuilder();
        BufferedReader br = null;

        try 
        {
            if (enc != null)
            {
                br = new BufferedReader(new InputStreamReader(is, enc));
            }
            else
            {
                // the below uses platform default char set which is UTF-8
                // https://developer.android.com/reference/java/nio/charset/Charset.html
                br = new BufferedReader(new InputStreamReader(is));
            }
        } 
        catch (Exception e) 
        {
            Log.e("JSONInputStream", 
                    String.format("Can't create BufferedReader [%s]", e.toString()));
            throw e;
        }

        // Read the stream until end
        String line = "";
        try 
        {
            while ((line = br.readLine()) != null) 
            { 
                sb.append(line); 
            }
        } 
        catch (IOException e) 
        {
            Log.e("JSONInputStream", 
                    String.format("Can't Can't read input stream [%s]", e.toString()));
            throw e;
        }


        return sb.toString();
    }


    private InputStream _is;
    private String _json;
}
 .

JSONOutputStream code:

 package com.JSONObjectSerialization;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;


/**
* @author dimast
* 
* This class can serialize classes to JSON strings.
* 
* This works for classes that contain only primitive fields.
*
*/
public class JSONOutputStream extends OutputStream
{
    /**
* Creates the stream.
* 
* @param os The output stream that receives the JSON output
*/
    public JSONOutputStream(final OutputStream os) 
    {
        _os = os;
    }

    @Override
    public final void close() 
    {
        if (_os != null) 
        {
            try 
            {
                _os.close();
            } 
            catch (IOException e) 
            {
                Log.e("JSONOutputStream",
                        String.format("Can't close output stream: %s", e.toString()));
            }
        }
    }

    /**
* Writes the JSON object representation to the output stream. 
* 
* @param object The object to that will be converted to JSON stream.
*/
    public final void writeObject(final Object object)
    {
        Field[] fields = object.getClass().getDeclaredFields();
        JSONObject jsonObj = new JSONObject();
        for (Field field : fields)
        {
            int modifiers = field.getModifiers();
            if (Modifier.isStatic(modifiers))
            {
                // skip statics
                continue;
            }

            try 
            {
                field.setAccessible(true);
                jsonObj.put(field.getName(), field.get(object));
            } 
            catch (IllegalArgumentException e) 
            {
                Log.e("JSONOutputStream",
                        String.format("Failed serializing field [%s] %s", field.getName(), e.toString()));
                continue;
            } 
            catch (JSONException e) 
            {
                Log.e("JSONOutputStream",
                        String.format("Failed serializing field [%s] %s", field.getName(), e.toString()));
                continue;
            } 
            catch (IllegalAccessException e) 
            {
                Log.e("JSONOutputStream",
                        String.format("Failed serializing field [%s] %s", field.getName(), e.toString()));
                continue;
            }
        }

        if (_os != null)
        {
            try 
            {
                // the below uses platform default char set which is UTF-8
                // https://developer.android.com/reference/java/nio/charset/Charset.html
                _os.write(jsonObj.toString().getBytes());
            } 
            catch (IOException e) 
            {
                Log.e("JSONOutputStream",
                        String.format("Failed writing to output stream: %s", e.toString()));
            }
        }
        else
        {
            Log.e("JSONOutputStream", "Can't write to output stream. _os is null.");
        }
    }

    @Override
    @Deprecated
    public void write(final int oneByte) throws IOException 
    {
        // do nothing
    } 

    private OutputStream _os;
}

Usage example:

  // Creating the initial object
        appendLog("Welcome!");
        SimpleClass sc = new SimpleClass(2.4, 17, true, "thisisit!");
        appendLog("This is the initial object:");
        appendLog(sc.toString());
        
        // Serializing it to JSON string
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        JSONOutputStream jos = new JSONOutputStream(baos);
        jos.writeObject(sc);
        jos.close();
        
        // Get the string form the output stream and print it
        String jsonString = baos.toString();
        appendLog("This is the serialized object:");
        appendLog(jsonString);
        
        // Deserialize the concrete object from the JSON string
        JSONInputStream jis = new JSONInputStream(jsonString);
        SimpleClass sc2 = null;
        try 
        {
            sc2 = jis.readObject(SimpleClass.class);
        } 
        catch (JSONStreamException e) 
        {
            Log.e("JSONObjectSerialization", "Failed to deserialize the object");
            return;
        }
        
        // Print the deserialized object
        appendLog("This is the de-serialized new object:");
        appendLog(sc2.toString());
        
        // Bye
        appendLog("Enjoy :)");

.