Support for parsing of JSON arrays seems to be good enough
authorJaroslav Tulach <jtulach@netbeans.org>
Thu, 24 Jul 2014 16:20:47 +0200
changeset 753a1f19913be01
parent 752 8991f98456e7
parent 750 166bc67759d0
child 754 ce30cad0ed95
Support for parsing of JSON arrays seems to be good enough
     1.1 --- a/json-tck/src/main/java/net/java/html/json/tests/ConvertTypesTest.java	Tue Jul 22 06:30:19 2014 +0200
     1.2 +++ b/json-tck/src/main/java/net/java/html/json/tests/ConvertTypesTest.java	Thu Jul 24 16:20:47 2014 +0200
     1.3 @@ -43,9 +43,12 @@
     1.4  package net.java.html.json.tests;
     1.5  
     1.6  import java.io.ByteArrayInputStream;
     1.7 +import java.io.EOFException;
     1.8  import java.io.InputStream;
     1.9  import java.io.UnsupportedEncodingException;
    1.10 +import java.util.ArrayList;
    1.11  import java.util.HashMap;
    1.12 +import java.util.List;
    1.13  import java.util.Map;
    1.14  import net.java.html.BrwsrCtx;
    1.15  import net.java.html.json.Models;
    1.16 @@ -56,18 +59,33 @@
    1.17   * @author Jaroslav Tulach <jtulach@netbeans.org>
    1.18   */
    1.19  public final class ConvertTypesTest {
    1.20 -    private static InputStream createIS(boolean includeSex, boolean includeAddress) 
    1.21 +    private static InputStream createIS(boolean includeSex, boolean includeAddress, int array) 
    1.22      throws UnsupportedEncodingException {
    1.23          StringBuilder sb = new StringBuilder();
    1.24 -        sb.append("{ \"firstName\" : \"son\",\n");
    1.25 -        sb.append("  \"lastName\" : \"dj\" \n");
    1.26 -        if (includeSex) {
    1.27 -            sb.append(",  \"sex\" : \"MALE\" \n");
    1.28 +        int repeat;
    1.29 +        if (array != -1) {
    1.30 +            sb.append("[\n");
    1.31 +            repeat = array;
    1.32 +        } else {
    1.33 +            repeat = 1;
    1.34          }
    1.35 -        if (includeAddress) {
    1.36 -            sb.append(",  \"address\" : { \"street\" : \"Schnirchova\" } \n");
    1.37 +        for (int i = 0; i < repeat; i++) {
    1.38 +            sb.append("{ \"firstName\" : \"son\",\n");
    1.39 +            sb.append("  \"lastName\" : \"dj\" \n");
    1.40 +            if (includeSex) {
    1.41 +                sb.append(",  \"sex\" : \"MALE\" \n");
    1.42 +            }
    1.43 +            if (includeAddress) {
    1.44 +                sb.append(",  \"address\" : { \"street\" : \"Schnirchova\" } \n");
    1.45 +            }
    1.46 +            sb.append("}\n");
    1.47 +            if (i < array - 1) {
    1.48 +                sb.append(",");
    1.49 +            }
    1.50          }
    1.51 -        sb.append("}\n");
    1.52 +        if (array != -1) {
    1.53 +            sb.append(']');
    1.54 +        }
    1.55          return new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
    1.56      }
    1.57      private static Object createJSON(boolean includeSex) 
    1.58 @@ -95,7 +113,7 @@
    1.59      @KOTest
    1.60      public void parseConvertToPeople() throws Exception {
    1.61          final BrwsrCtx c = newContext();
    1.62 -        final InputStream o = createIS(true, false);
    1.63 +        final InputStream o = createIS(true, false, -1);
    1.64          
    1.65          Person p = Models.parse(c, Person.class, o);
    1.66          
    1.67 @@ -107,7 +125,7 @@
    1.68      @KOTest
    1.69      public void parseConvertToPeopleWithAddress() throws Exception {
    1.70          final BrwsrCtx c = newContext();
    1.71 -        final InputStream o = createIS(true, true);
    1.72 +        final InputStream o = createIS(true, true, -1);
    1.73          
    1.74          Person p = Models.parse(c, Person.class, o);
    1.75          
    1.76 @@ -119,6 +137,59 @@
    1.77      }
    1.78  
    1.79      @KOTest
    1.80 +    public void parseConvertToPeopleWithAddressIntoAnArray() throws Exception {
    1.81 +        final BrwsrCtx c = newContext();
    1.82 +        final InputStream o = createIS(true, true, -1);
    1.83 +        
    1.84 +        List<Person> arr = new ArrayList<Person>();
    1.85 +        Models.parse(c, Person.class, o, arr);
    1.86 +        
    1.87 +        assert arr.size() == 1 : "There is one item in " + arr;
    1.88 +        
    1.89 +        Person p = arr.get(0);
    1.90 +        assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
    1.91 +        assert "dj".equals(p.getLastName()) : "Last name: " + p.getLastName();
    1.92 +        assert Sex.MALE.equals(p.getSex()) : "Sex: " + p.getSex();
    1.93 +        assert p.getAddress() != null : "Some address provided";
    1.94 +        assert p.getAddress().getStreet().equals("Schnirchova") : "Is Schnirchova: " + p.getAddress();
    1.95 +    }
    1.96 +    
    1.97 +    @KOTest 
    1.98 +    public void parseNullValue() throws Exception {
    1.99 +        final BrwsrCtx c = newContext();
   1.100 +        
   1.101 +        StringBuilder sb = new StringBuilder();
   1.102 +        sb.append("{ \"firstName\" : \"son\",\n");
   1.103 +        sb.append("  \"lastName\" : null } \n");  
   1.104 +        
   1.105 +        final ByteArrayInputStream is = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
   1.106 +        Person p = Models.parse(c, Person.class, is);
   1.107 +
   1.108 +        assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
   1.109 +        assert null == p.getLastName() : "Last name: " + p.getLastName();
   1.110 +    }
   1.111 +
   1.112 +    @KOTest 
   1.113 +    public void parseNullArrayValue() throws Exception {
   1.114 +        final BrwsrCtx c = newContext();
   1.115 +        
   1.116 +        StringBuilder sb = new StringBuilder();
   1.117 +        sb.append("[ null, { \"firstName\" : \"son\",\n");
   1.118 +        sb.append("  \"lastName\" : null } ]\n");  
   1.119 +        
   1.120 +        final ByteArrayInputStream is = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
   1.121 +        List<Person> arr = new ArrayList<Person>();
   1.122 +        Models.parse(c, Person.class, is, arr);
   1.123 +        
   1.124 +        assert arr.size() == 2 : "There are two items in " + arr;
   1.125 +        assert arr.get(0) == null : "first is null " + arr;
   1.126 +        
   1.127 +        Person p = arr.get(1);
   1.128 +        assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
   1.129 +        assert null == p.getLastName() : "Last name: " + p.getLastName();
   1.130 +    }
   1.131 +
   1.132 +    @KOTest
   1.133      public void testConvertToPeopleWithoutSex() throws Exception {
   1.134          final Object o = createJSON(false);
   1.135          
   1.136 @@ -132,7 +203,7 @@
   1.137      @KOTest
   1.138      public void parseConvertToPeopleWithoutSex() throws Exception {
   1.139          final BrwsrCtx c = newContext();
   1.140 -        final InputStream o = createIS(false, false);
   1.141 +        final InputStream o = createIS(false, false, -1);
   1.142          Person p = Models.parse(c, Person.class, o);
   1.143          
   1.144          assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
   1.145 @@ -140,6 +211,73 @@
   1.146          assert p.getSex() == null : "No sex: " + p.getSex();
   1.147      }
   1.148      
   1.149 +    @KOTest
   1.150 +    public void parseConvertToPeopleWithAddressOnArray() throws Exception {
   1.151 +        final BrwsrCtx c = newContext();
   1.152 +        final InputStream o = createIS(true, true, 1);
   1.153 +        
   1.154 +        Person p = Models.parse(c, Person.class, o);
   1.155 +        
   1.156 +        assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
   1.157 +        assert "dj".equals(p.getLastName()) : "Last name: " + p.getLastName();
   1.158 +        assert Sex.MALE.equals(p.getSex()) : "Sex: " + p.getSex();
   1.159 +        assert p.getAddress() != null : "Some address provided";
   1.160 +        assert p.getAddress().getStreet().equals("Schnirchova") : "Is Schnirchova: " + p.getAddress();
   1.161 +    }
   1.162 +
   1.163 +    @KOTest
   1.164 +    public void parseConvertToPeopleWithoutSexOnArray() throws Exception {
   1.165 +        final BrwsrCtx c = newContext();
   1.166 +        final InputStream o = createIS(false, false, 1);
   1.167 +        Person p = Models.parse(c, Person.class, o);
   1.168 +        
   1.169 +        assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
   1.170 +        assert "dj".equals(p.getLastName()) : "Last name: " + p.getLastName();
   1.171 +        assert p.getSex() == null : "No sex: " + p.getSex();
   1.172 +    }
   1.173 +
   1.174 +    @KOTest
   1.175 +    public void parseFirstElementFromAbiggerArray() throws Exception {
   1.176 +        final BrwsrCtx c = newContext();
   1.177 +        final InputStream o = createIS(false, false, 5);
   1.178 +        Person p = Models.parse(c, Person.class, o);
   1.179 +        
   1.180 +        assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
   1.181 +        assert "dj".equals(p.getLastName()) : "Last name: " + p.getLastName();
   1.182 +        assert p.getSex() == null : "No sex: " + p.getSex();
   1.183 +    }
   1.184 +
   1.185 +    @KOTest
   1.186 +    public void parseAllElementFromAbiggerArray() throws Exception {
   1.187 +        final BrwsrCtx c = newContext();
   1.188 +        final InputStream o = createIS(false, false, 5);
   1.189 +        
   1.190 +        List<Person> res = new ArrayList<Person>();
   1.191 +        Models.parse(c, Person.class, o, res);
   1.192 +        
   1.193 +        assert res.size() == 5 : "Five elements found" + res;
   1.194 +        
   1.195 +        for (Person p : res) {
   1.196 +            assert "son".equals(p.getFirstName()) : "First name: " + p.getFirstName();
   1.197 +            assert "dj".equals(p.getLastName()) : "Last name: " + p.getLastName();
   1.198 +            assert p.getSex() == null : "No sex: " + p.getSex();
   1.199 +        }
   1.200 +    }
   1.201 +    
   1.202 +    @KOTest
   1.203 +    public void parseOnEmptyArray() throws Exception {
   1.204 +        final BrwsrCtx c = newContext();
   1.205 +        final InputStream o = createIS(false, false, 0);
   1.206 +        
   1.207 +        try {
   1.208 +            Models.parse(c, Person.class, o);
   1.209 +        } catch (EOFException ex) {
   1.210 +            // OK
   1.211 +            return;
   1.212 +        }
   1.213 +        throw new IllegalStateException("Should throw end of file exception, as the array is empty");
   1.214 +    }
   1.215 +    
   1.216      private static BrwsrCtx newContext() {
   1.217          return Utils.newContext(ConvertTypesTest.class);
   1.218      }
     2.1 --- a/json/src/main/java/net/java/html/json/Models.java	Tue Jul 22 06:30:19 2014 +0200
     2.2 +++ b/json/src/main/java/net/java/html/json/Models.java	Thu Jul 24 16:20:47 2014 +0200
     2.3 @@ -45,6 +45,7 @@
     2.4  import net.java.html.BrwsrCtx;
     2.5  import java.io.IOException;
     2.6  import java.io.InputStream;
     2.7 +import java.util.Collection;
     2.8  import org.netbeans.html.json.impl.JSON;
     2.9  
    2.10  /** Information about and 
    2.11 @@ -90,7 +91,29 @@
    2.12       * @since 0.2
    2.13       */
    2.14      public static <M> M parse(BrwsrCtx c, Class<M> model, InputStream is) throws IOException {
    2.15 -        return JSON.readStream(c, model, is);
    2.16 +        return JSON.readStream(c, model, is, null);
    2.17 +    }
    2.18 +    
    2.19 +    /** Generic method to parse stream, that can possibly contain array
    2.20 +     * of specified objects.
    2.21 +     * 
    2.22 +     * @param <M> the type of the individal JSON object
    2.23 +     * @param c context of the technology to use for reading 
    2.24 +     * @param model the model class generated by {@link Model} annotation
    2.25 +     * @param is input stream with data
    2.26 +     * @param collectTo collection to add the individual model instances to.
    2.27 +     *   If the stream contains an object, one instance will be added, if
    2.28 +     *   it contains an array, the number of array items will be added to
    2.29 +     *   the collection
    2.30 +     * @throws IOException thrown when an I/O problem appears
    2.31 +     * @since 0.8.3
    2.32 +     */
    2.33 +    public static <M> void parse(
    2.34 +        BrwsrCtx c, Class<M> model, 
    2.35 +        InputStream is, Collection<? super M> collectTo
    2.36 +    ) throws IOException {
    2.37 +        collectTo.getClass();
    2.38 +        JSON.readStream(c, model, is, collectTo);
    2.39      }
    2.40      
    2.41      /** Converts an existing, raw, JSON object into a {@link Model model class}.
     3.1 --- a/json/src/main/java/org/apidesign/html/json/spi/Transfer.java	Tue Jul 22 06:30:19 2014 +0200
     3.2 +++ b/json/src/main/java/org/apidesign/html/json/spi/Transfer.java	Thu Jul 24 16:20:47 2014 +0200
     3.3 @@ -68,7 +68,10 @@
     3.4      /** Reads content of a stream and creates its JSON representation.
     3.5       * The returned object is implementation dependant. It however needs
     3.6       * to be acceptable as first argument of {@link #extract(java.lang.Object, java.lang.String[], java.lang.Object[]) extract}
     3.7 -     * method.
     3.8 +     * method. If the stream contains representation or a JSON array,
     3.9 +     * an Object[] should be returned - each of its members should, by itself
    3.10 +     * be acceptable argument to 
    3.11 +     * the {@link #extract(java.lang.Object, java.lang.String[], java.lang.Object[]) extract} method.
    3.12       * 
    3.13       * @param is input stream to read data from
    3.14       * @return an object representing the JSON data
     4.1 --- a/json/src/main/java/org/netbeans/html/json/impl/JSON.java	Tue Jul 22 06:30:19 2014 +0200
     4.2 +++ b/json/src/main/java/org/netbeans/html/json/impl/JSON.java	Thu Jul 24 16:20:47 2014 +0200
     4.3 @@ -42,6 +42,7 @@
     4.4   */
     4.5  package org.netbeans.html.json.impl;
     4.6  
     4.7 +import java.io.EOFException;
     4.8  import java.io.IOException;
     4.9  import java.io.InputStream;
    4.10  import java.util.Collection;
    4.11 @@ -404,10 +405,28 @@
    4.12          return PropertyBindingAccessor.clone(from, model, c);
    4.13      }
    4.14      
    4.15 -    public static <T> T readStream(BrwsrCtx c, Class<T> modelClazz, InputStream data) 
    4.16 +    public static <T> T readStream(BrwsrCtx c, Class<T> modelClazz, InputStream data, Collection<? super T> collectTo) 
    4.17      throws IOException {
    4.18          Transfer tr = findTransfer(c);
    4.19 -        return read(c, modelClazz, tr.toJSON((InputStream)data));
    4.20 +        Object rawJSON = tr.toJSON((InputStream)data);
    4.21 +        if (rawJSON instanceof Object[]) {
    4.22 +            final Object[] arr = (Object[])rawJSON;
    4.23 +            if (collectTo != null) {
    4.24 +                for (int i = 0; i < arr.length; i++) {
    4.25 +                    collectTo.add(read(c, modelClazz, arr[i]));
    4.26 +                }
    4.27 +                return null;
    4.28 +            }
    4.29 +            if (arr.length == 0) {
    4.30 +                throw new EOFException("Recieved an empty array");
    4.31 +            }
    4.32 +            rawJSON = arr[0];
    4.33 +        }
    4.34 +        T res = read(c, modelClazz, rawJSON);
    4.35 +        if (collectTo != null) {
    4.36 +            collectTo.add(res);
    4.37 +        }
    4.38 +        return res;
    4.39      }
    4.40      public static <T> T read(BrwsrCtx c, Class<T> modelClazz, Object data) {
    4.41          if (data == null) {
     5.1 --- a/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java	Tue Jul 22 06:30:19 2014 +0200
     5.2 +++ b/ko-ws-tyrus/src/main/java/org/netbeans/html/wstyrus/LoadJSON.java	Thu Jul 24 16:20:47 2014 +0200
     5.3 @@ -123,43 +123,15 @@
     5.4              final PushbackInputStream is = new PushbackInputStream(
     5.5                  conn.getInputStream(), 1
     5.6              );
     5.7 -            boolean array = false;
     5.8 -            boolean string = false;
     5.9 -            if (call.isJSONP()) {
    5.10 -                for (;;) {
    5.11 -                    int ch = is.read();
    5.12 -                    if (ch == -1) {
    5.13 -                        break;
    5.14 -                    }
    5.15 -                    if (ch == '[') {
    5.16 -                        is.unread(ch);
    5.17 -                        array = true;
    5.18 -                        break;
    5.19 -                    }
    5.20 -                    if (ch == '{') {
    5.21 -                        is.unread(ch);
    5.22 -                        break;
    5.23 -                    }
    5.24 -                }
    5.25 -            } else {
    5.26 -                int ch = is.read();
    5.27 -                if (ch == -1) {
    5.28 -                    string = true;
    5.29 -                } else {
    5.30 -                    array = ch == '[';
    5.31 -                    is.unread(ch);
    5.32 -                    if (!array && ch != '{') {
    5.33 -                        string = true;
    5.34 -                    }
    5.35 -                }
    5.36 -            }
    5.37 +            boolean[] arrayOrString = { false, false };
    5.38 +            detectJSONType(call.isJSONP(), is, arrayOrString);
    5.39              try {
    5.40 -                if (string) {
    5.41 +                if (arrayOrString[1]) {
    5.42                      throw new JSONException("");
    5.43                  }
    5.44                  JSONTokener tok = createTokener(is);
    5.45                  Object obj;
    5.46 -                obj = array ? new JSONArray(tok) : new JSONObject(tok);
    5.47 +                obj = arrayOrString[0] ? new JSONArray(tok) : new JSONObject(tok);
    5.48                  json = convertToArray(obj);
    5.49              } catch (JSONException ex) {
    5.50                  Reader r = new InputStreamReader(is, "UTF-8");
    5.51 @@ -184,6 +156,34 @@
    5.52          }
    5.53      }
    5.54  
    5.55 +    private static void detectJSONType(boolean skipAnything, final PushbackInputStream is, boolean[] arrayOrString) throws IOException {
    5.56 +        for (;;) {
    5.57 +            int ch = is.read();
    5.58 +            if (ch == -1) {
    5.59 +                arrayOrString[1] = true;
    5.60 +                break;
    5.61 +            }
    5.62 +            if (Character.isWhitespace(ch)) {
    5.63 +                continue;
    5.64 +            }
    5.65 +
    5.66 +            if (ch == '[') {
    5.67 +                is.unread(ch);
    5.68 +                arrayOrString[0] = true;
    5.69 +                break;
    5.70 +            }
    5.71 +            if (ch == '{') {
    5.72 +                is.unread(ch);
    5.73 +                break;
    5.74 +            }
    5.75 +            if (!skipAnything) {
    5.76 +                is.unread(ch);
    5.77 +                arrayOrString[1] = true;
    5.78 +                break;
    5.79 +            }
    5.80 +        }
    5.81 +    }
    5.82 +
    5.83      private static JSONTokener createTokener(InputStream is) throws IOException {
    5.84          Reader r = new InputStreamReader(is, "UTF-8");
    5.85          try {
    5.86 @@ -218,6 +218,8 @@
    5.87                  obj.put(key, convertToArray(obj.get(key)));
    5.88              }
    5.89              return obj;
    5.90 +        } else if (o == JSONObject.NULL) {
    5.91 +            return null;
    5.92          } else {
    5.93              return o;
    5.94          }
    5.95 @@ -227,11 +229,11 @@
    5.96          if (jsonObject instanceof JSONObject) {
    5.97              JSONObject obj = (JSONObject)jsonObject;
    5.98              for (int i = 0; i < props.length; i++) {
    5.99 -                try {
   5.100 -                    values[i] = obj.has(props[i]) ? obj.get(props[i]) : null;
   5.101 -                } catch (JSONException ex) {
   5.102 -                    LoadJSON.LOG.log(Level.SEVERE, "Can't read " + props[i] + " from " + jsonObject, ex);
   5.103 +                Object val = obj.opt(props[i]);
   5.104 +                if (val == JSONObject.NULL) {
   5.105 +                    val = null;
   5.106                  }
   5.107 +                values[i] = val;
   5.108              }
   5.109              return;
   5.110          }
   5.111 @@ -252,8 +254,12 @@
   5.112      
   5.113      public static Object parse(InputStream is) throws IOException {
   5.114          try {
   5.115 -            JSONTokener t = createTokener(is);
   5.116 -            return new JSONObject(t);
   5.117 +            PushbackInputStream push = new PushbackInputStream(is, 1);
   5.118 +            boolean[] arrayOrString = { false, false };
   5.119 +            detectJSONType(false, push, arrayOrString);
   5.120 +            JSONTokener t = createTokener(push);
   5.121 +            Object obj = arrayOrString[0] ? new JSONArray(t) : new JSONObject(t);
   5.122 +            return convertToArray(obj);
   5.123          } catch (JSONException ex) {
   5.124              throw new IOException(ex);
   5.125          }
     6.1 --- a/ko4j/src/main/java/org/netbeans/html/ko4j/LoadJSON.java	Tue Jul 22 06:30:19 2014 +0200
     6.2 +++ b/ko4j/src/main/java/org/netbeans/html/ko4j/LoadJSON.java	Thu Jul 24 16:20:47 2014 +0200
     6.3 @@ -166,10 +166,11 @@
     6.4          + "  if (request.readyState !== 4) return;\n"
     6.5          + "  var r = request.response || request.responseText;\n"
     6.6          + "  try {\n"
     6.7 +        + "    if (request.status >= 400) throw request.status + ': ' + request.statusText;"
     6.8          + "    try { r = eval('(' + r + ')'); } catch (ignore) { }"
     6.9          + "    done.@org.apidesign.html.json.spi.JSONCall::notifySuccess(Ljava/lang/Object;)(r);\n"
    6.10          + "  } catch (error) {;\n"
    6.11 -        + "    @org.netbeans.html.ko4j.LoadJSON::notifyError(Ljava/lang/Object;Ljava/lang/Object;)(done, r);\n"
    6.12 +        + "    @org.netbeans.html.ko4j.LoadJSON::notifyError(Ljava/lang/Object;Ljava/lang/Object;)(done, error);\n"
    6.13          + "  }\n"
    6.14          + "};\n"
    6.15          + "request.onerror = function (e) {\n"
     7.1 --- a/src/main/javadoc/overview.html	Tue Jul 22 06:30:19 2014 +0200
     7.2 +++ b/src/main/javadoc/overview.html	Thu Jul 24 16:20:47 2014 +0200
     7.3 @@ -80,7 +80,10 @@
     7.4          <p>
     7.5              Setters or array properties on classes generated by {@link net.java.html.json.Model}
     7.6              annotation can be accessed from any thread. {@link org.apidesign.html.sound.spi.AudioEnvironment}
     7.7 -            can be registered into {@link net.java.html.BrwsrCtx}.
     7.8 +            can be registered into {@link net.java.html.BrwsrCtx}. There is
     7.9 +            a {@link net.java.html.json.Models#parse(net.java.html.BrwsrCtx, java.lang.Class, java.io.InputStream, java.util.Collection)  method}
    7.10 +            to parse a JSON array and convert it into 
    7.11 +            {@link net.java.html.json.Model model classes}.
    7.12          </p>
    7.13          
    7.14          <h3>What's New in Version 0.8.2?</h3>