sexta-feira, 18 de dezembro de 2009

Serializador Java para JSON - Parte I

JSON é uma forma de transferência de dados que está sendo utilizado bastante atualmente. Possui um formato bastante que deixa o conjunto de dados bastante enxuto.

Desenvolvi estas classes baseadas na biblioteca FlexJson: http://flexjson.sourceforge.net/ . Fiz algumas alterações que deixou a biblioteca bem menor. Mas, minha motivação principal foi que eu pudesse utilizar a biblioteca  na versão 1.4 do Java. Ao todo, a biblioteca ficou com apenas quatro classes: ChainedIterator, ChainedSet, JSONSerializer, ObjectVisitor. Vou colocar o código de todas elas, e também um exemplo de uso.

Código da classse ChainedIterator

import java.util.Iterator;
import java.util.Iterator;
import java.util.Set;

public class ChainedIterator implements Iterator {
    Iterator[] iterators;
    int current = 0;

    public ChainedIterator(Set sets) {
     Object[] arraySets = sets.toArray(); 
        iterators = new Iterator[sets.size()];
        for( int i = 0; i < sets.size(); i++ ) {
            iterators[i] = ((Set)arraySets[i]).iterator();
        }
    }

    public boolean hasNext() {
        if( iterators[current].hasNext() ) {
            return true;
        } else {
            current++;
            return current < iterators.length && iterators[current].hasNext();
        }
    }

    public Object next() {
        return iterators[current].next();
    }

    public void remove() {
        iterators[current].remove();
    }
}

Código da classe ChainedSet
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Collection;
import java.util.TreeSet;

public class ChainedSet implements Set {
    Set parent;
    Set child;

    public ChainedSet(Set parent) {
        this.parent = parent;
        this.child = new HashSet();
    }

    public int size() {
        return this.child.size() + parent.size();
    }

    public boolean isEmpty() {
        return this.child.isEmpty() && parent.isEmpty();
    }

    public boolean contains(Object o) {
        return child.contains(o) || parent.contains(o);
    }

    public Iterator iterator() {
     Set sets = new TreeSet();
     sets.add(child);
     sets.add(parent);
        return new ChainedIterator(sets);
    }

    public Object[] toArray() {
        Object[] carr = child.toArray();
        Object[] parr = parent.toArray();
        Object[] combined = new Object[ carr.length + parr.length ];
        System.arraycopy( carr, 0, combined, 0, carr.length );
        System.arraycopy( parr, 0, combined, carr.length, parr.length );
        return combined;
    }

    public Object[] toArray(Object[] a) {
        throw new IllegalStateException( "Not implemeneted" );
    }

    public boolean add(Object o) {
        return child.add( o );
    }

    public boolean remove(Object o) {
        return child.remove( o );
    }

    public boolean containsAll(Collection c) {
        return child.containsAll(c) || parent.containsAll(c); 
    }

    public boolean addAll(Collection c) {
        return child.addAll( c );
    }

    public boolean retainAll(Collection c) {
        return child.retainAll( c );
    }

    public boolean removeAll(Collection c) {
        return child.removeAll( c );
    }

    public void clear() {
        child.clear();
    }

    public Set getParent() {
        return parent;
    }
}
Código da classe JSONSerializer
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JSONSerializer {

    public final static char[] HEX = "0123456789ABCDEF".toCharArray();

    List pathExpressions = new ArrayList();
    Map transformations = new HashMap();

    public JSONSerializer() {
    }

    public String serialize( String rootName, Object target ) {
        return new ObjectVisitor().visit( rootName, target );
    }

    public String serialize( Object target ) {
        return new ObjectVisitor().visit( target );
    }

    public String prettyPrint( Object target ) {
        return new ObjectVisitor( true ).visit( target );
    }

    public String prettyPrint( String rootName, Object target ) {
        return new ObjectVisitor( true ).visit( rootName, target );
    }
}

Código da classe ObjectVisitor
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;

public class ObjectVisitor {
    protected StringBuffer builder;
    protected boolean prettyPrint = false;
    private int amount = 0;
    private boolean insideArray = false;

    protected ObjectVisitor() {
        builder = new StringBuffer();
    }

    public ObjectVisitor(boolean prettyPrint) {
        this();
        this.prettyPrint = prettyPrint;
    }

    public String visit( Object target ) {
        json( target );
        return builder.toString();
    }

    public String visit( String rootName, Object target ) {
        beginObject();
        string(rootName);
        add(':');
        json( target );
        endObject();
        return builder.toString();
    }

    private void numberToString(Number n) {
  if (n != null && testValidity(n)) {
   String s = n.toString();
   if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) {
    while (s.endsWith("0")) {
     s = s.substring(0, s.length() - 1);
    }
    if (s.endsWith(".")) {
     s = s.substring(0, s.length() - 1);
    }
   }
   add(s);
  }else{
            add("0");
  }
 }
    
    private boolean testValidity(Object o) {
        if (o != null) {
            if (o instanceof Double) {
                if (((Double)o).isInfinite() || ((Double)o).isNaN()) {
                    return false;
                }
            } else if (o instanceof Float) {
                if (((Float)o).isInfinite() || ((Float)o).isNaN()) {
                    return false;
                }
            }
        }
        return true;
    }
    
    
    private void json(Object object) {
        if (object == null) add("null");
        else if (object instanceof Class)
            string( ((Class)object).getName() );
        else if (object instanceof Boolean)
            bool( ((Boolean) object) );
        else if (object instanceof String)
            string(object);
        else if (object instanceof Character)
            string(object);
        else if (object instanceof Number)
            numberToString((Number) object);
        else if (object instanceof Map)
            map( (Map)object);
        else if (object.getClass().isArray())
            array( object );
        else if( object instanceof Date)             
            date( (Date)object );
        else if (object instanceof Collection)
            array(((Collection) object).iterator() );            
        else
            bean( object );
    }

    private void map(Map map) {
        beginObject();
        Iterator it = map.keySet().iterator();
        boolean firstField = true;
        while (it.hasNext()) {
            Object key = it.next();
            int len = builder.length();
            add( key, map.get(key), firstField );
            if( len < builder.length() ) {
                firstField = false;
            }
        }
        endObject();
    }

    private void array(Iterator it) {
        beginArray();
        while (it.hasNext()) {
            if( prettyPrint ) {
                addNewline();
            }
            addArrayElement( it.next(), it.hasNext() );
        }
        endArray();
    }

    private void array(Object object) {
        beginArray();
        int length = Array.getLength(object);
        for (int i = 0; i < length; ++i) {
            if( prettyPrint ) {
                addNewline();
            }
            addArrayElement( Array.get(object, i), i < length - 1 );
        }
        endArray();
    }

    private void addArrayElement(Object object, boolean isLast ) {
        int len = builder.length();
        json( object );
        if( len < builder.length() ) { // make sure we at least added an element.
            if ( isLast ) add(',');
        }
    }

    private void bool(Boolean b) {
        add( b.booleanValue() ? "true" : "false" );
    }

    private void string(Object obj) {
        add('"');
        CharacterIterator it = new StringCharacterIterator(obj.toString());
        for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
            if (c == '"') add("\\\"");
            else if (c == '\\') add("\\\\");
            else if (c == '\b') add("\\b");
            else if (c == '\f') add("\\f");
            else if (c == '\n') add("\\n");
            else if (c == '\r') add("\\r");
            else if (c == '\t') add("\\t");
            else if (Character.isISOControl(c)) {
                unicode(c);
            } else {
                add(c);
            }
        }            
        add('"');
    }

    private void date(Date date) {
        builder.append( date.getTime());
    }

    private ChainedSet visits = new ChainedSet( Collections.EMPTY_SET );

    protected void bean(Object bean) {
        if( !visits.contains( bean ) ) {
            visits = new ChainedSet( visits );
            visits.add( bean );
            beginObject();
            try {
                Class klass = bean.getClass();

                Method[] methods = klass.getMethods();
                boolean firstField = true;
                for (int i = 0; i < methods.length; i += 1) {
                    Method method = methods[i];
                    if (Modifier.isPublic(method.getModifiers())) {
                        String name = method.getName();
                        String key = "";
                        boolean invoke = false;
                        if (name.startsWith("get")) {
                         invoke = true;
                            key = name.substring(3);
                        } else if (name.startsWith("is")) {
                            key = name.substring(2);
                            invoke = true;
                        }
                        if (key.length() > 0){
                         key = String.valueOf(key.charAt(0)).toLowerCase() + key.substring(1);
                        }
                        
                        if (invoke) {
                            Object value = method.invoke(bean, (Object[])null);
                            
                            if( !visits.contains( value ) ) {
                             add(key, value, firstField);
                             firstField = false;
                            }
                        }
                    }
                }
            } catch( Exception e ) {
             
            }
            endObject();
            visits = (ChainedSet) visits.getParent();
        }
    }
    
    protected boolean addComma(boolean firstField) {
        if ( !firstField ) {
            add(',');
        } else {
            firstField = false;
        }
        return firstField;
    }

    protected void beginObject() {
        if( prettyPrint ) {
            if( insideArray ) {
                indent( amount );
            }
            amount += 4;
        }
        add( '{' );
    }

    protected void endObject() {
        if( prettyPrint ) {
            addNewline();
            amount -= 4;
            indent( amount );
        }
        add( '}' );
    }

    private void beginArray() {
        if( prettyPrint ) {
            amount += 4;
            insideArray = true;
        }
        add('[');
    }

    private void endArray() {
        if( prettyPrint ) {
            addNewline();
            amount -= 4;
            insideArray = false;
            indent( amount );
        }
        add(']');
    }

    protected void add( char c ) {
        builder.append( c );
    }

    private void indent(int amount) {
        for( int i = 0; i < amount; i++ ) {
            builder.append( " " );
        }
    }

    private void addNewline() {
        builder.append("\n");
    }

    protected void add( Object value ) {
        builder.append( value );
    }

    protected void add(Object key, Object value, boolean prependComma) {
        int start = builder.length();
        addComma( prependComma );
        addAttribute( key );

        int len = builder.length();
        json( value );
        if( len == builder.length() ) {
            builder.delete( start, len ); // erase the attribute key we didn't output anything.
        }
    }

    private void addAttribute(Object key) {
        if( prettyPrint ) {
            addNewline();
            indent( amount );
        }
        builder.append("\"");
        builder.append( key );
        builder.append( "\"" );
        builder.append( ":" );
        if( prettyPrint ) {
            builder.append(" ");
        }
    }

    private void unicode(char c) {
        add("\\u");
        int n = c;
        for (int i = 0; i < 4; ++i) {
            int digit = (n & 0xf000) >> 12;
            add(JSONSerializer.HEX[digit]);
            n <<= 4;
        }
    }
}


Nenhum comentário: