An automated JSON Encoder and Decoder for Java

I posed a little while ago that I’d implemented a JSON Encoding that worked a bit like Java Serialization. It works as such in that you don’t need to encode the objects by hand. As long as you follow some basic rules, the code will automagically turn your Java objects into JSON, and the JSON back into Java objects again. Well, I finally got my act together and the implementation is below. Consider this Public Domain code now! If it doesn’t work for you, it’s not my problem (that said, I’d like to know if it doesn’t work).

Before The implementation, heres a few things to know about it:

  1. It solves the Date problem in such a way that it’s transparent to the JavaScript code!
    1. Dates are encoded as “new Date(UTCTime)”
    2. They are decoded against a collection of date-formats (listed at the top of the JSONDecoder class)
  2. The encoding and decoding work against Reader and Writer objects, and thus have declared IOExceptions
    1. They expect to be closely tied to the Reader and Writer provided by the Servlet Container
  3. They both conform to the Java Beans specs, so if it’s not a bean property, it’s ignored
  4. The decoder can handle both the abstract Collection types and concrete types
    1. public void setUserList(List<User> users)
    2. public void setUserList(ArrayList<User> users)
    3. The Collections must have a solid type declared as their content (in the generic)
  5. Map objects are encoded as JavaScript objects
    1. Map keys must therefore be Strings
    2. The Map keys should be valid JavaScript identifiers

If you are going to use this code, I would appreciate a comment here (just to let me know).


import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.IntrospectionException;

import java.io.Writer;
import java.io.IOException;
import java.io.StringWriter;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Date;
import java.util.List;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.WeakHashMap;

/**
 * Created on 2008/08/20
 * @author Jason Morris
 */
public class JSONEncoder<T> {
    private static final Map<Class<?>, ObjectEncoder> encoderCache =
            Collections.synchronizedMap(new WeakHashMap<Class<?>, ObjectEncoder>());

    private final Encoder encoder;

    private JSONEncoder(final Class<T> type) throws IntrospectionException {
        encoder = getEncoder(type);
    }

    @SuppressWarnings("unchecked")
    private static Encoder getEncoder(final Class<?> type) throws IntrospectionException {
        if(CharSequence.class.isAssignableFrom(type) || Character.class.equals(type) ||
                Character.TYPE.equals(type) || UUID.class.equals(type)) {

            return StringEncoder.INSTANCE;
        } else if(Number.class.isAssignableFrom(type) ||
                Byte.TYPE.equals(type) || Short.TYPE.equals(type) ||
                Integer.TYPE.equals(type) || Long.TYPE.equals(type) ||
                Boolean.TYPE.equals(type) || Boolean.class.equals(type)) {

            return PlainFormEncoder.INSTANCE;
        } else if(type.isArray()) {
            return new ArrayEncoder(getEncoder(type.getComponentType()));
        } else if(Iterable.class.isAssignableFrom(type)) {
            return IterableEncoder.INSTANCE;
        } else if(Map.class.isAssignableFrom(type)) {
            return MapEncoder.INSTANCE;
        } else if(Date.class.isAssignableFrom(type)) {
            return DateEncoder.INSTANCE;
        } else {
            ObjectEncoder encoder = encoderCache.get(type);

            if(encoder == null) {
                encoder = new ObjectEncoder(type);
                encoderCache.put(type, encoder);
            }

            return encoder;
        }
    }

    public String encode(final T value) {
        final StringWriter out = new StringWriter();

        try {
            encode(value, out);
        } catch(IOException ex) {
            // ignore this... it won't happen with a StringWriter
        }

        return out.toString();
    }

    public void encode(final T value, final Writer out) throws IOException {
        encoder.encode(value, out);
    }

    public static <T> JSONEncoder<T> getJSONEncoder(final Class<T> type) throws IntrospectionException {
        return new JSONEncoder<T>(type);
    }

    private static class Property {
        private final String name;

        private final Method getter;

        private final Encoder encoder;

        public Property(final String name, final Method getter) throws IntrospectionException {
            this.name = name;
            this.getter = getter;
            this.encoder = getEncoder(getter.getReturnType());
        }

        private void encode(final Object object, final Writer out) throws IOException {
            try {
                out.write(name);
                out.write(':');

                final Object value = getter.invoke(object);

                if(value == null) {
                    out.write("null");
                } else {
                    encoder.encode(value, out);
                }
            } catch(IllegalAccessException ex) {
                throw new IOException(ex);
            } catch(IllegalArgumentException ex) {
                throw new IOException(ex);
            } catch(InvocationTargetException ex) {
                throw new IOException(ex);
            }
        }

    }

    private static interface Encoder {
        void encode(Object value, Writer out) throws IOException;

    }

    private static class PlainFormEncoder implements Encoder {
        private static final PlainFormEncoder INSTANCE = new PlainFormEncoder();

        public PlainFormEncoder() {
        }

        public void encode(final Object value, final Writer out) throws IOException {
            out.write(value.toString());
        }

    }

    private static class DateEncoder implements Encoder {
        private static final DateEncoder INSTANCE = new DateEncoder();

        public DateEncoder() {
        }

        public void encode(final Object value, final Writer out) throws IOException {
            out.write("new Date(");
            out.write(Long.toString(((Date)value).getTime()));
            out.write(")");
        }

    }

    private static class StringEncoder implements Encoder {
        private static final StringEncoder INSTANCE = new StringEncoder();

        public StringEncoder() {
        }

        public void encode(final Object value, final Writer out) throws IOException {
            out.write('\"');

            final String string = value.toString();

            for(int i = 0; i < string.length(); i++) {
                final char ch = string.charAt(i);

                switch(ch) {
                    case '\n':
                        out.write("\\n");
                        break;
                    case '\r':
                        out.write("\\r");
                        break;
                    case '\"':
                        out.write("\\\"");
                        break;
                    default:
                        out.write(ch);
                        break;
                }
            }

            out.write('\"');
        }

    }

    private static class ArrayEncoder implements Encoder {
        private final Encoder encoder;

        public ArrayEncoder(final Encoder encoder) {
            this.encoder = encoder;
        }

        public void encode(final Object value, final Writer out) throws IOException {
            out.write('&#91;');

            final int length = Array.getLength(value);

            for(int i = 0; i < length; i++) {
                encoder.encode(Array.get(value, i), out);

                if(i + 1 < length) {
                    out.write(',');
                }
            }

            out.write('&#93;');
        }

    }

    private static class IterableEncoder implements Encoder {
        private static final IterableEncoder INSTANCE = new IterableEncoder();

        public IterableEncoder() {
        }

        public void encode(final Object value, final Writer out) throws IOException {
            final Iterator<? extends Object> iterator = ((Iterable<?>)value).iterator();

            Encoder lastEncoder = null;
            Class<?> lastType = null;

            out.write('[');

            while(iterator.hasNext()) {
                try {
                    final Object instance = iterator.next();
                    final Class<?> type = instance.getClass();
                    final Encoder enc = type == lastType
                            ? lastEncoder
                            : getEncoder(type);

                    enc.encode(instance, out);

                    lastType = type;
                    lastEncoder = enc;
                } catch(IntrospectionException ie) {
                    throw new IOException(ie);
                }

                if(iterator.hasNext()) {
                    out.write(',');
                }
            }

            out.write(']');
        }

    }

    private static class MapEncoder implements Encoder {
        private static final MapEncoder INSTANCE = new MapEncoder();

        public MapEncoder() {
        }

        public void encode(final Object value, final Writer out) throws IOException {
            @SuppressWarnings("unchecked")
            final Map<Object, Object> map = (Map<Object, Object>)value;
            final Set<Map.Entry<Object, Object>> entrySet = map.entrySet();
            final Iterator<Map.Entry<Object, Object>> iterator = entrySet.iterator();

            Encoder lastEncoder = null;
            Class<?> lastType = null;

            out.write('{');

            while(iterator.hasNext()) {
                try {
                    final Map.Entry<Object, Object> entry = iterator.next();
                    final Object entryValue = entry.getValue();
                    final Class<?> type = entryValue.getClass();
                    final Encoder enc = type == lastType
                            ? lastEncoder
                            : getEncoder(type);

                    out.write(entry.getKey().toString());
                    out.write(':');
                    enc.encode(entryValue, out);

                    lastType = type;
                    lastEncoder = enc;
                } catch(IntrospectionException ie) {
                    throw new IOException(ie);
                }

                if(iterator.hasNext()) {
                    out.write(',');
                }
            }

            out.write('}');
        }

    }

    private static class ObjectEncoder implements Encoder {
        private final Property[] properties;

        public ObjectEncoder(final Class<?> type) throws IntrospectionException {
            final BeanInfo info = Introspector.getBeanInfo(type);
            final PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
            final List<Property> props = new ArrayList<Property>(descriptors.length);

            for(int i = 0; i < descriptors.length; i++) {
                final PropertyDescriptor d = descriptors&#91;i&#93;;
                final Method read = d.getReadMethod();

                if(!read.getDeclaringClass().equals(Object.class)) {
                    props.add(new Property(d.getName(), read));
                }
            }

            properties = props.toArray(new Property&#91;props.size()&#93;);
        }

        public void encode(final Object value, final Writer out) throws IOException {
            out.write('{');

            final int length = properties.length;

            for(int i = 0; i < length; i++) {
                properties&#91;i&#93;.encode(value, out);

                if(i + 1 < length) {
                    out.write(',');
                }
            }

            out.write('}');
        }

    }
}

&#91;/sourcecode&#93;
That was the encoder, now for the decoder:

&#91;sourcecode language="Java"&#93;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.IntrospectionException;

import java.io.Reader;
import java.io.IOException;
import java.io.StringReader;
import java.io.StreamTokenizer;

import java.lang.reflect.Type;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import java.util.Map;
import java.util.Set;
import java.util.Date;
import java.util.List;
import java.util.Queue;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeSet;
import java.util.SortedSet;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Collection;
import java.util.Collections;
import java.util.WeakHashMap;
import java.util.NavigableSet;

/**
 * Created on 2008/08/20
 * @author Jason Morris
 */
public class JSONDecoder {
    private static final DateFormat&#91;&#93; DATE_FORMATS = new DateFormat&#91;&#93;{
        new SimpleDateFormat("yyyy-MM-dd"),
        new SimpleDateFormat("yyyyMMdd"),
        new SimpleDateFormat("yyyyMMdd'T'HHmmss")
    };

    private static final Map<Class<?>, Map<String, PropertyDescriptor>> PROPERTY_CACHE =
            Collections.synchronizedMap(new WeakHashMap<Class<?>, Map<String, PropertyDescriptor>>());

    public static <T> T decode(final String json, final Class<T> type) {
        try {
            return decode(new StringReader(json), type);
        } catch(IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    public static <T> T decode(final Reader in, final Class<T> type) throws IOException {
        final ObjectBuilder builder = createObjectBuilder(type);
        final JSONScanner scanner = new JSONScanner(in);
        scanner.consumeObject(builder);
        @SuppressWarnings("unchecked")
        final T value = (T)builder.createObject();

        return value;
    }

    private static Map<String, PropertyDescriptor> createClassPropertyMap(final Class<?> type) throws IntrospectionException {
        final BeanInfo info = Introspector.getBeanInfo(type);
        final PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
        final int len = descriptors.length;
        final Map<String, PropertyDescriptor> properties = new HashMap<String, PropertyDescriptor>(len);

        for(final PropertyDescriptor desc : descriptors) {
            if(desc.getWriteMethod() != null) {
                properties.put(desc.getName(), desc);
            }
        }

        return properties;
    }

    private static Map<String, PropertyDescriptor> getClassProperties(final Class<?> type) throws IntrospectionException {
        Map<String, PropertyDescriptor> properties = PROPERTY_CACHE.get(type);

        synchronized(PROPERTY_CACHE) {
            if(properties == null) {
                properties = createClassPropertyMap(type);
                PROPERTY_CACHE.put(type, properties);
            }
        }

        return properties;
    }

    private static void testParameterizedType(final ParameterizedType type,
            final Class<?> expectedRawType, final Class<?>... parameters) throws IOException {

        final Type rawType = type.getRawType();

        if(rawType instanceof Class) {
            final Class<?> rawClass = (Class<?>)rawType;
            if(!expectedRawType.isAssignableFrom(rawClass)) {
                throw new IOException("Expected a raw type of " + expectedRawType.getName() +
                        " but got " + rawClass.getName());
            }
        } else {
            throw new IOException("Expected a class as the raw type of " + type);
        }

        final Type[] paramTypes = type.getActualTypeArguments();

        if(paramTypes.length != parameters.length) {
            throw new IOException("Incorrect number of type parameters, expected " +
                    parameters.length + " but found " + paramTypes.length + ": " + type);
        }

        for(int i = 0; i < parameters.length; i++) {
            if(paramTypes&#91;i&#93; instanceof Class) {
                final Class<?> paramClass = (Class<?>)paramTypes[i];
                final Class<?> expected = parameters[i];

                if(expected != null && !expected.isAssignableFrom(paramClass)) {
                    throw new IOException("Exptected a parameter of type " +
                            expected.getName() + " but got " + paramClass.getName());
                }
            } else {
                throw new IOException("Expected a class parameter,  but got " + paramTypes[i]);
            }
        }
    }

    private static ObjectBuilder createObjectBuilder(final Type type) throws IOException {
        if(type == null) {
            return new NullObjectBuilder();
        } else if(type instanceof Class) {
            try {
                return new DefaultObjectBuilder((Class<?>)type);
            } catch(InstantiationException ex) {
                throw new IOException(ex);
            } catch(IllegalAccessException ex) {
                throw new IOException(ex);
            } catch(IntrospectionException ex) {
                throw new IOException(ex);
            }
        } else if(type instanceof ParameterizedType) {
            final ParameterizedType paramType = (ParameterizedType)type;
            testParameterizedType(paramType, Map.class, String.class, null);
            return new MapObjectBuilder(paramType.getActualTypeArguments()[1]);
        } else {
            throw new IOException("Cannot handle objects of type " + type);
        }
    }

    private static Object convertNumber(final double num, final Class<?> type) throws IOException {
        if(type == byte.class || type == Byte.class) {
            return Byte.valueOf((byte)num);
        } else if(type == short.class || type == Short.class) {
            return Short.valueOf((short)num);
        } else if(type == int.class || type == Integer.class) {
            return Integer.valueOf((int)num);
        } else if(type == long.class || type == Long.class) {
            return Long.valueOf((long)num);
        } else if(type == float.class || type == Float.class) {
            return Float.valueOf((float)num);
        } else if(type == double.class || type == Double.class) {
            return Double.valueOf(num);
        } else {
            return null;
        }
    }

    private static Object convertString(final String value, final Class<?> type) throws IOException {
        if(type == String.class || CharSequence.class.isAssignableFrom(type)) {
            return value;
        } else if(type == char.class || type == Character.class) {
            if(value.length() == 0) {
                return Character.valueOf('');
            } else {
                return Character.valueOf(value.charAt(0));
            }
        } else {
            return null;
        }
    }

    private static interface ObjectBuilder {
        Type getFieldType(final String fieldName);

        void set(final String fieldName, final Object builder) throws IOException;

        Object createObject();

    }

    private static class NullObjectBuilder implements ObjectBuilder {
        public NullObjectBuilder() {
        }

        public Type getFieldType(final String fieldName) {
            return null;
        }

        public void set(final String fieldName, final Object builder) {
        }

        public Object createObject() {
            return null;
        }

    }

    private static class DefaultObjectBuilder implements ObjectBuilder {
        private final Map<String, PropertyDescriptor> properties;

        private final Object object;

        DefaultObjectBuilder(Class<?> type) throws InstantiationException, IllegalAccessException, IntrospectionException {
            properties = getClassProperties(type);
            object = type.newInstance();
        }

        public Type getFieldType(final String fieldName) {
            final PropertyDescriptor desc = properties.get(fieldName);
            return desc != null
                    ? desc.getWriteMethod().getGenericParameterTypes()[0]
                    : null;
        }

        public void set(final String fieldName, final Object value) throws IOException {
            final PropertyDescriptor desc = properties.get(fieldName);

            if(desc != null && value != null) {
                try {
                    desc.getWriteMethod().invoke(object, value);
                } catch(IllegalAccessException iae) {
                    throw new IOException(iae);
                } catch(IllegalArgumentException iae) {
                    throw new IOException(iae);
                } catch(InvocationTargetException ite) {
                    throw new IOException(ite);
                }
            }
        }

        public Object createObject() {
            return object;
        }

    }

    private static class MapObjectBuilder implements ObjectBuilder {
        private final Type valueType;

        private final Map<String, Object> map = new HashMap<String, Object>();

        public MapObjectBuilder(Type valueType) {
            this.valueType = valueType;
        }

        public Type getFieldType(final String fieldName) {
            return valueType;
        }

        public void set(final String fieldName, final Object value) throws IOException {
            map.put(fieldName, value);
        }

        public Object createObject() {
            return map;
        }

    }

    private static class JSONScanner {
        private final StreamTokenizer in;

        JSONScanner(final Reader in) {
            this.in = new StreamTokenizer(in);
            configureTokenizer();
        }

        private void configureTokenizer() {
            in.quoteChar('\'');
            in.quoteChar('\"');
            in.eolIsSignificant(false);
            in.ordinaryChar(':');
            in.ordinaryChar('[');
            in.ordinaryChar(']');
            in.ordinaryChar('{');
            in.ordinaryChar('}');
            in.ordinaryChar(',');
        }

        @SuppressWarnings("unchecked")
        private <T> void consumeJSONArray(final Class<T> valueType, final Collection<T> objects) throws IOException {
            boolean done = false;
            while(!done) {
                if(in.nextToken() == ']') {
                    done = true;
                } else {
                    in.pushBack();
                    objects.add((T)consumeValue(valueType));
                }

                if(in.nextToken() != ',') {
                    in.pushBack();
                }
            }
        }

        private <T> Object consumeJavaArray(final Class<T> valueType) throws IOException {
            final List<T> objects = new ArrayList<T>();
            consumeJSONArray(valueType, objects);
            return toArrayHelper(objects, valueType);
        }

        @SuppressWarnings("unchecked")
        private <T> Object consumeJavaCollection(final Type arrayType) throws IOException {
            final ParameterizedType paramType = (ParameterizedType)arrayType;
            testParameterizedType(paramType, Collection.class, Object.class);

            final Class<?> rawClass = (Class<?>)paramType.getRawType();
            final Class<T> valueType = (Class<T>)paramType.getActualTypeArguments()[0];

            Collection<T> output = null;

            if(rawClass.isInterface()) {
                if(rawClass.equals(List.class) || rawClass.equals(Collection.class)) {
                    output = new ArrayList<T>();
                } else if(rawClass.equals(Set.class)) {
                    output = new HashSet<T>();
                } else if(rawClass.equals(SortedSet.class) || rawClass.equals(NavigableSet.class)) {
                    output = new TreeSet<T>();
                } else if(rawClass.equals(Queue.class)) {
                    output = new LinkedList<T>();
                } else {
                    throw new IOException("Unknown Collection type: " + rawClass.getName());
                }
            } else if(Modifier.isAbstract(rawClass.getModifiers())) {
                throw new IOException("Cannot work with an abstract Collection: " + rawClass);
            } else {
                try {
                    output = (Collection<T>)rawClass.newInstance();
                } catch(InstantiationException ie) {
                    throw new IOException(ie);
                } catch(IllegalAccessException iae) {
                    throw new IOException(iae);
                }
            }

            consumeJSONArray(valueType, output);

            return output;
        }

        @SuppressWarnings("unchecked")
        private <T> T[] toArrayHelper(final List<T> values, final Class<T> componentType) {
            return values.toArray((T[])Array.newInstance(componentType, values.size()));
        }

        void consume(final int expected) throws IOException {
            if(in.nextToken() != expected) {
                throw new IOException("Expected " + (char)expected +
                        " but instead found " + (char)in.ttype);
            }
        }

        void consume(final String expected) throws IOException {
            if(in.nextToken() != StreamTokenizer.TT_WORD) {
                throw new IOException("Expected a word.");
            }

            if(!in.sval.equals(expected)) {
                throw new IOException("Expected '" + expected + "' but found '" +
                        in.sval + "' instead");
            }
        }

        Object consumeArray(final Type arrayType) throws IOException {
            if(arrayType == null) {
                return null;
            }

            if(arrayType instanceof Class) {
                final Class<?> type = (Class<?>)arrayType;

                if(!type.isArray()) {
                    throw new IOException("Expected an array type,  but got: " + type.toString());
                }

                return consumeJavaArray(type.getComponentType());
            } else if(arrayType instanceof ParameterizedType) {
                return consumeJavaCollection(arrayType);
            } else {
                throw new IOException("Expected either an array type or ParameterizedType");
            }
        }

        Date consumeDate() throws IOException, IOException {
            consume("Date");
            consume('(');

            Date value = null;

            switch(in.nextToken()) {
                case StreamTokenizer.TT_NUMBER:
                    value = new Date((long)in.nval);
                    break;
                case '\'':
                case '\"':
                    for(final DateFormat fmt : DATE_FORMATS) {
                        synchronized(fmt) {
                            try {
                                value = fmt.parse(in.sval);
                                break;
                            } catch(ParseException ex) {
                                // ignore this
                            }
                        }
                    }
                    break;
                default:
                    throw new IOException("Expected an ISO date / datetime string, or " + "a millisecond count.");
            }

            if(in.nextToken() != StreamTokenizer.TT_NUMBER) {
                throw new IOException("Expected a word.");
            }

            consume(')');

            return value;
        }

        Object consumeValue(final Type genericType) throws IOException {
            switch(in.nextToken()) {
                case '{':
                    final ObjectBuilder fieldObject = createObjectBuilder(genericType);
                    in.pushBack();
                    consumeObject(fieldObject);
                    return fieldObject.createObject();
                case '\"':
                case '\'':
                    return convertString(in.sval, (Class<?>)genericType);
                case '[':
                    return consumeArray(genericType);
                case StreamTokenizer.TT_NUMBER:
                    return convertNumber(in.nval, (Class<?>)genericType);
                case StreamTokenizer.TT_WORD:
                    if(in.sval.equals("true") || in.sval.equals("false")) {
                        return Boolean.valueOf(in.sval);
                    } else if(in.sval.equals("new")) {
                        return consumeDate();
                    } else if(in.sval.equals("null")) {
                        return null;
                    } else {
                        throw new IOException("Expected 'true', 'false' or 'new', but got '" +
                                in.sval + "'");
                    }
            }

            return null;
        }

        boolean consumeField(final ObjectBuilder into) throws IOException {
            consume(StreamTokenizer.TT_WORD);
            final String identifier = in.sval;
            consume(':');

            into.set(identifier, consumeValue(into.getFieldType(identifier)));

            switch(in.nextToken()) {
                case ',':
                    return true;
                case '}':
                    in.pushBack();
                    return false;
                default:
                    throw new IOException("Expected one of ',' or '}' but got '" + (char)in.ttype + "'");
            }
        }

        void consumeObject(final ObjectBuilder into) throws IOException {
            consume('{');

            while(consumeField(into)) {
                ;
            }

            consume('}');
        }

    }
}

Advertisements

8 Responses to “An automated JSON Encoder and Decoder for Java”

  1. Josh W Says:

    Useful change to handle quoted identifiers as generated by php’s json_encode.

    String consumeIdentifier() throws IOException {
    switch(in.nextToken()){
    case ‘\”‘:
    case ‘\”:
    case StreamTokenizer.TT_WORD:
    return in.sval;
    default:
    throw new IOException(“Expected identifier”);
    }
    }

    boolean consumeField(final ObjectBuilder into) throws IOException {
    final String identifier = consumeIdentifier();
    consume(‘:’);

    into.set(identifier, consumeValue(into.getFieldType(identifier)));

    switch(in.nextToken()) {
    case ‘,’:
    return true;
    case ‘}’:
    in.pushBack();
    return false;
    default:
    throw new IOException(“Expected one of ‘,’ or ‘}’ but got ‘” + (char)in.ttype + “‘”);
    }
    }

  2. Jaka Jancar Says:

    Hi!

    I’m most likely going to use this code. Do you have any updates since you posted it?

    • Jason Says:

      Hey there Jaka,

      I don’t have any updates of my own, although if you’re using PHP you may want to use the code Josh W posted above to handle quoted identifiers.

      Hope you find the code useful!

  3. MidnightJava Says:

    Nicely done. I’m planning on using the encoder for a simple application, so your lightweight approach is just what I need. Using an encoder factory is elegant and makes it easy to use.

    I went ahead and loaded the Decoder into my IDE, and line 179 is flagged a compile error. You have

    return Character.valueOf(”);

    This essentially amounts to the Character class wrapping a null primitive value, which of course isn’t allowed. I can’t see what else to do here except return null. But I probably won’t test it, since I don’t have a use for the Decoder right now.

    -Mark L.

  4. Archie Russell Says:

    Do you have any plans to make this pretty-print? I got it to work but tough to read the output. Also I was getting a null pointer exception at

    if(!read.getDeclaringClass().equals(Object.class)) {

    I guess sometimes “read” is null?

    Thanks

  5. Christo Says:

    Hi..

    Planning to use your code for decoding. Can you publish a sample of using these classes(JSONEncoder and JSONDecoder)?

    My need is to decode below json output:

    { “sections”:[ { “sectionID”:”1″,”sectionName”:”Personal Banking”,”products”:[ { “productID”:”100″,”productName”:”Test Product 1″,”productDesc”:”This is a test description for Product 1.”,”productMainImage”:”/media/63788/jsub-5201281.jpg”,”productSuppImages”:[],”filters”:[“200″],”relatedProducts”:[“101″],”productPDF”:””},{“productID”:”101″,”productName”:”Test Product 2″,”productDesc”:”This is a test description for Product 2.”,”productMainImage”:”/media/63788/jsub-5201281.jpg”,”productSuppImages”:[],”filters”:[“200″,”201″],”relatedProducts”:[“100″],”productPDF”:””}]}]}

    Thanks in advance
    Christo

  6. sandiro Says:

    failing test case : import java.beans.IntrospectionException;import java.util.ArrayList;import java.util.List;
    public class User { private List usedIp = new ArrayList();private String username;public List getUsedIp() {return usedIp;}public void setUsedIp(List usedIp) {this.usedIp = usedIp;}
    public String getXsername() {return username;}public void setXsername(String username) {this.username = username;}
    public static void main(String[] args){ try {JSONDecoder.decode( JSONEncoder.getJSONEncoder(User.class).encode(new User()), User.class );} catch (IntrospectionException e) {e.printStackTrace();}}}

    Exception in thread “main” java.lang.RuntimeException: java.io.IOException: Expected one of ‘,’ or ‘}’ but got ‘?’
    string after list seems failing ….

  7. Antonio Says:

    I’m using your code. Very nice and usefull.
    Many thanks

    Antonio


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

%d bloggers like this: