code: public static String serializeObject(final Object obj, final int indentWidth,
              final boolean onlySerializePublicFields, final ClassFieldCache classFieldCache) {
          final HashMap<ReferenceEqualityKey<Object>, JSONObject> objToJSONVal = new HashMap<>();
  
          final Object rootJsonVal = toJSONGraph(obj, new HashSet<ReferenceEqualityKey<Object>>(),
                  new HashSet<ReferenceEqualityKey<Object>>(), classFieldCache, objToJSONVal,
                  onlySerializePublicFields);
  
          final Map<ReferenceEqualityKey<JSONReference>, CharSequence> jsonReferenceToId = new HashMap<>();
          final AtomicInteger objId = new AtomicInteger(0);
          assignObjectIds(rootJsonVal, objToJSONVal, classFieldCache, jsonReferenceToId, objId,
                  onlySerializePublicFields);
  
          final StringBuilder buf = new StringBuilder(32768);
          jsonValToJSONString(rootJsonVal, jsonReferenceToId, /* includeNullValuedFields = */ false, 0, indentWidth,
                  buf);
          return buf.toString();
      }
docs: Recursively serialize an Object (or array, list, map or set of objects) to JSON, skipping transient and final
  fields.
  
  @param obj
  The root object of the object graph to serialize.
  @param indentWidth
  If indentWidth == 0, no prettyprinting indentation is performed, otherwise this specifies the
  number of spaces to indent each level of JSON.
  @param onlySerializePublicFields
  If true, only serialize public fields.
  @param classFieldCache
  The class field cache. Reusing this cache will increase the speed if many JSON documents of the
  same type need to be produced.
  @return The object graph in JSON form.
  @throws IllegalArgumentException
  If anything goes wrong during serialization.