Converting Nested Json files to CSV in java with reflection

Objective:

convert nested json to csv. The csv header should contain fields from each level and separated by ‘_’.

Example:

{
  "firstName": "John",
  "lastName": "Smith",
  "isAlive": true,
  "age": 25,
  "address": {
    "streetAddress": "9513 Key west ave",
    "city": "Rockville",
    "state": "MD",
    "postalCode": "20874"
  },
  "phoneNumbers": 
    {
      "type": "home",
      "number": "212 555-1234"
    },
  "children": {},
  "spouse": null
}

Header should become: firstName, lastName, isAlive, age, address_streeAddress, address_city, address_state, address_postalCode, phoneNumbers_type….etc

Actions:

1. deserialize the json to the mapped java object using gson or jackson.

2. append header first using reflection

    StringBuilder sb = new StringBuilder();
    appendAllDeclaredFields(YourClass.class, YourClass.class.getPackage().getName(), "", sb);
    /**
     *
     * @param c The class to get fields from
     * @param rootPackage The root package to compare(we only get fields from the same package to exclude other java class)
     * @param parentName The parent class name so that we could combine all its path.
     * @param sb the string builder to append values with comma delimited
     * <p/>
     * This function will search recursively and append all the fields
     * MyObject{int fileld1, OtherObject, other}, OtherObject{int filed2, String filed3}  --> filed1,other_filed2,otherfield3
     */
    void appendAllDeclaredFields(Class c, String rootPackage, String parentName, StringBuilder sb)
    {
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields)
        {
            Class filedClass = field.getType();
            String fieldName = field.getName();
            //if we have declaredfileds and the filed is in in the same package  as root and filed is not Enum, we continue search
            if (filedClass.getDeclaredFields().length > 0 && filedClass.getPackage().getName().contains(rootPackage) && !filedClass.isEnum())
            {
                appendAllDeclaredFields(filedClass, rootPackage, getCombinedName(parentName, fieldName), sb);
            }
            //If it is plain fields like String/int/bigDecimal, we append the filed name.
            else
            {
                sb.append(",").append(getCombinedName(parentName, fieldName));
            }
        }
    }

    private String getCombinedName(String parentName, String fieldName)
    {
        return "".equals(parentName) ? fieldName : parentName + "_" + fieldName;
    }

3. append values with reflection

This is usually called in a loop of all your target data objects needed to be dumped.

    /**
     *
     * @param c The class to get fields from
     * @param rootPackage The root package to compare(we only get fields from the same package to exclude other java class)
     * @param target the target object to get value from
     * @param sb the string builder to append values with comma delimited
     *
     * @throws IllegalAccessException This function will search recursively and append all the values of the 'target' Object.
     */
    void appendDeclaredFieldValues(Class c, String rootPackage, Object target, StringBuilder sb) throws IllegalAccessException
    {
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields)
        {
            Class filedClass = field.getType();
            field.setAccessible(true);
            Object childObject = null;
            try
            {
                //try to get the object value from the 'target' Object
                childObject = field.get(target);
            }
            catch (Exception e)
            {
                //do nothing, just a try to get value, exception is expected with empty columns
            }
            //if we have declaredfileds and the filed is in in the same package  as root and filed is not Enum, we continue search
            if (filedClass.getDeclaredFields().length > 0 && filedClass.getPackage().getName().contains(rootPackage) && !filedClass.isEnum())
            {
                appendDeclaredFieldValues(filedClass, rootPackage, childObject, sb);
            }
            //If it is plain fields like String/int/bigDecimal, we append the filed value.
            else
            {
                //Since this is served as CSV, we do not want the object value contains comma which would break the formatting.
                sb.append(",").append(String.valueOf(childObject).replaceAll(",", "").replaceAll("(\r\n|\n)", ""));
            }

        }
    }

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s