2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
20 package org
.apache
.thrift
.protocol
;
22 import java
.io
.IOException
;
23 import java
.nio
.ByteBuffer
;
24 import java
.nio
.charset
.StandardCharsets
;
25 import java
.util
.ArrayList
;
26 import java
.util
.Stack
;
28 import org
.apache
.thrift
.TByteArrayOutputStream
;
29 import org
.apache
.thrift
.TException
;
30 import org
.apache
.thrift
.transport
.TTransport
;
33 * JSON protocol implementation for thrift.
35 * This is a full-featured protocol supporting write and read.
37 * Please see the C++ class header for a detailed description of the
38 * protocol's wire format.
41 public class TJSONProtocol
extends TProtocol
{
44 * Factory for JSON protocol objects
46 public static class Factory
implements TProtocolFactory
{
47 protected boolean fieldNamesAsString_
= false;
51 public Factory(boolean fieldNamesAsString
) {
52 fieldNamesAsString_
= fieldNamesAsString
;
55 public TProtocol
getProtocol(TTransport trans
) {
56 return new TJSONProtocol(trans
, fieldNamesAsString_
);
61 private static final byte[] COMMA
= new byte[] {','};
62 private static final byte[] COLON
= new byte[] {':'};
63 private static final byte[] LBRACE
= new byte[] {'{'};
64 private static final byte[] RBRACE
= new byte[] {'}'};
65 private static final byte[] LBRACKET
= new byte[] {'['};
66 private static final byte[] RBRACKET
= new byte[] {']'};
67 private static final byte[] QUOTE
= new byte[] {'"'};
68 private static final byte[] BACKSLASH
= new byte[] {'\\'};
69 private static final byte[] ZERO
= new byte[] {'0'};
71 private static final byte[] ESCSEQ
= new byte[] {'\\','u','0','0'};
73 private static final long VERSION
= 1;
75 private static final byte[] JSON_CHAR_TABLE
= {
76 /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
77 0, 0, 0, 0, 0, 0, 0, 0,'b','t','n', 0,'f','r', 0, 0, // 0
78 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
79 1, 1,'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
82 private static final String ESCAPE_CHARS
= "\"\\/bfnrt";
84 private static final byte[] ESCAPE_CHAR_VALS
= {
85 '"', '\\', '/', '\b', '\f', '\n', '\r', '\t',
88 private static final int DEF_STRING_SIZE
= 16;
90 private static final byte[] NAME_BOOL
= new byte[] {'t', 'f'};
91 private static final byte[] NAME_BYTE
= new byte[] {'i','8'};
92 private static final byte[] NAME_I16
= new byte[] {'i','1','6'};
93 private static final byte[] NAME_I32
= new byte[] {'i','3','2'};
94 private static final byte[] NAME_I64
= new byte[] {'i','6','4'};
95 private static final byte[] NAME_DOUBLE
= new byte[] {'d','b','l'};
96 private static final byte[] NAME_STRUCT
= new byte[] {'r','e','c'};
97 private static final byte[] NAME_STRING
= new byte[] {'s','t','r'};
98 private static final byte[] NAME_MAP
= new byte[] {'m','a','p'};
99 private static final byte[] NAME_LIST
= new byte[] {'l','s','t'};
100 private static final byte[] NAME_SET
= new byte[] {'s','e','t'};
102 private static final TStruct ANONYMOUS_STRUCT
= new TStruct();
104 private static final byte[] getTypeNameForTypeID(byte typeID
)
130 throw new TProtocolException(TProtocolException
.NOT_IMPLEMENTED
,
131 "Unrecognized type");
135 private static final byte getTypeIDForTypeName(byte[] name
)
137 byte result
= TType
.STOP
;
138 if (name
.length
> 1) {
141 result
= TType
.DOUBLE
;
166 result
= TType
.STRUCT
;
169 if (name
[1] == 't') {
170 result
= TType
.STRING
;
172 else if (name
[1] == 'e') {
181 if (result
== TType
.STOP
) {
182 throw new TProtocolException(TProtocolException
.NOT_IMPLEMENTED
,
183 "Unrecognized type");
188 // Base class for tracking JSON contexts that may require inserting/reading
189 // additional JSON syntax characters
190 // This base context does nothing.
191 protected class JSONBaseContext
{
192 protected void write() throws TException
{}
194 protected void read() throws TException
{}
196 protected boolean escapeNum() { return false; }
199 // Context for JSON lists. Will insert/read commas before each item except
201 protected class JSONListContext
extends JSONBaseContext
{
202 private boolean first_
= true;
205 protected void write() throws TException
{
214 protected void read() throws TException
{
218 readJSONSyntaxChar(COMMA
);
223 // Context for JSON records. Will insert/read colons before the value portion
224 // of each record pair, and commas before each key except the first. In
225 // addition, will indicate that numbers in the key position need to be
226 // escaped in quotes (since JSON keys must be strings).
227 protected class JSONPairContext
extends JSONBaseContext
{
228 private boolean first_
= true;
229 private boolean colon_
= true;
232 protected void write() throws TException
{
237 trans_
.write(colon_ ? COLON
: COMMA
);
243 protected void read() throws TException
{
248 readJSONSyntaxChar(colon_ ? COLON
: COMMA
);
254 protected boolean escapeNum() {
259 // Holds up to one byte from the transport
260 protected class LookaheadReader
{
262 private boolean hasData_
;
263 private byte[] data_
= new byte[1];
265 // Return and consume the next byte to be read, either taking it from the
266 // data buffer if present or getting it from the transport otherwise.
267 protected byte read() throws TException
{
272 trans_
.readAll(data_
, 0, 1);
277 // Return the next byte to be read without consuming, filling the data
278 // buffer if it has not been filled already.
279 protected byte peek() throws TException
{
281 trans_
.readAll(data_
, 0, 1);
288 // Stack of nested contexts that we may be in
289 private Stack
<JSONBaseContext
> contextStack_
= new Stack
<JSONBaseContext
>();
291 // Current context that we are in
292 private JSONBaseContext context_
= new JSONBaseContext();
294 // Reader that manages a 1-byte buffer
295 private LookaheadReader reader_
= new LookaheadReader();
297 // Write out the TField names as a string instead of the default integer value
298 private boolean fieldNamesAsString_
= false;
300 // Push a new JSON context onto the stack.
301 private void pushContext(JSONBaseContext c
) {
302 contextStack_
.push(context_
);
306 // Pop the last JSON context off the stack
307 private void popContext() {
308 context_
= contextStack_
.pop();
311 // Reset the context stack to its initial state
312 private void resetContext() {
313 while (!contextStack_
.isEmpty()) {
321 public TJSONProtocol(TTransport trans
) {
325 public TJSONProtocol(TTransport trans
, boolean fieldNamesAsString
) {
327 fieldNamesAsString_
= fieldNamesAsString
;
331 public void reset() {
332 contextStack_
.clear();
333 context_
= new JSONBaseContext();
334 reader_
= new LookaheadReader();
337 // Temporary buffer used by several methods
338 private byte[] tmpbuf_
= new byte[4];
340 // Read a byte that must match b[0]; otherwise an exception is thrown.
341 // Marked protected to avoid synthetic accessor in JSONListContext.read
342 // and JSONPairContext.read
343 protected void readJSONSyntaxChar(byte[] b
) throws TException
{
344 byte ch
= reader_
.read();
346 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
347 "Unexpected character:" + (char)ch
);
351 // Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its
352 // corresponding hex value
353 private static final byte hexVal(byte ch
) throws TException
{
354 if ((ch
>= '0') && (ch
<= '9')) {
355 return (byte)((char)ch
- '0');
357 else if ((ch
>= 'a') && (ch
<= 'f')) {
358 return (byte)((char)ch
- 'a' + 10);
361 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
362 "Expected hex character");
366 // Convert a byte containing a hex value to its corresponding hex character
367 private static final byte hexChar(byte val
) {
370 return (byte)((char)val
+ '0');
373 return (byte)((char)(val
- 10) + 'a');
377 // Write the bytes in array buf as a JSON characters, escaping as needed
378 private void writeJSONString(byte[] b
) throws TException
{
382 for (int i
= 0; i
< len
; i
++) {
383 if ((b
[i
] & 0x00FF) >= 0x30) {
384 if (b
[i
] == BACKSLASH
[0]) {
385 trans_
.write(BACKSLASH
);
386 trans_
.write(BACKSLASH
);
389 trans_
.write(b
, i
, 1);
393 tmpbuf_
[0] = JSON_CHAR_TABLE
[b
[i
]];
394 if (tmpbuf_
[0] == 1) {
395 trans_
.write(b
, i
, 1);
397 else if (tmpbuf_
[0] > 1) {
398 trans_
.write(BACKSLASH
);
399 trans_
.write(tmpbuf_
, 0, 1);
402 trans_
.write(ESCSEQ
);
403 tmpbuf_
[0] = hexChar((byte)(b
[i
] >> 4));
404 tmpbuf_
[1] = hexChar(b
[i
]);
405 trans_
.write(tmpbuf_
, 0, 2);
412 // Write out number as a JSON value. If the context dictates so, it will be
413 // wrapped in quotes to output as a JSON string.
414 private void writeJSONInteger(long num
) throws TException
{
416 String str
= Long
.toString(num
);
417 boolean escapeNum
= context_
.escapeNum();
421 byte[] buf
= str
.getBytes(StandardCharsets
.UTF_8
);
428 // Write out a double as a JSON value. If it is NaN or infinity or if the
429 // context dictates escaping, write out as JSON string.
430 private void writeJSONDouble(double num
) throws TException
{
432 String str
= Double
.toString(num
);
433 boolean special
= false;
434 switch (str
.charAt(0)) {
436 case 'I': // Infinity
440 if (str
.charAt(1) == 'I') { // -Infinity
448 boolean escapeNum
= special
|| context_
.escapeNum();
452 byte[] b
= str
.getBytes(StandardCharsets
.UTF_8
);
453 trans_
.write(b
, 0, b
.length
);
459 // Write out contents of byte array b as a JSON string with base-64 encoded
461 private void writeJSONBase64(byte[] b
, int offset
, int length
) throws TException
{
467 // Encode 3 bytes at a time
468 TBase64Utils
.encode(b
, off
, 3, tmpbuf_
, 0);
469 trans_
.write(tmpbuf_
, 0, 4);
475 TBase64Utils
.encode(b
, off
, len
, tmpbuf_
, 0);
476 trans_
.write(tmpbuf_
, 0, len
+ 1);
481 private void writeJSONObjectStart() throws TException
{
483 trans_
.write(LBRACE
);
484 pushContext(new JSONPairContext());
487 private void writeJSONObjectEnd() throws TException
{
489 trans_
.write(RBRACE
);
492 private void writeJSONArrayStart() throws TException
{
494 trans_
.write(LBRACKET
);
495 pushContext(new JSONListContext());
498 private void writeJSONArrayEnd() throws TException
{
500 trans_
.write(RBRACKET
);
504 public void writeMessageBegin(TMessage message
) throws TException
{
505 resetContext(); // THRIFT-3743
506 writeJSONArrayStart();
507 writeJSONInteger(VERSION
);
508 byte[] b
= message
.name
.getBytes(StandardCharsets
.UTF_8
);
510 writeJSONInteger(message
.type
);
511 writeJSONInteger(message
.seqid
);
515 public void writeMessageEnd() throws TException
{
520 public void writeStructBegin(TStruct struct
) throws TException
{
521 writeJSONObjectStart();
525 public void writeStructEnd() throws TException
{
526 writeJSONObjectEnd();
530 public void writeFieldBegin(TField field
) throws TException
{
531 if (fieldNamesAsString_
) {
532 writeString(field
.name
);
534 writeJSONInteger(field
.id
);
536 writeJSONObjectStart();
537 writeJSONString(getTypeNameForTypeID(field
.type
));
541 public void writeFieldEnd() throws TException
{
542 writeJSONObjectEnd();
546 public void writeFieldStop() {}
549 public void writeMapBegin(TMap map
) throws TException
{
550 writeJSONArrayStart();
551 writeJSONString(getTypeNameForTypeID(map
.keyType
));
552 writeJSONString(getTypeNameForTypeID(map
.valueType
));
553 writeJSONInteger(map
.size
);
554 writeJSONObjectStart();
558 public void writeMapEnd() throws TException
{
559 writeJSONObjectEnd();
564 public void writeListBegin(TList list
) throws TException
{
565 writeJSONArrayStart();
566 writeJSONString(getTypeNameForTypeID(list
.elemType
));
567 writeJSONInteger(list
.size
);
571 public void writeListEnd() throws TException
{
576 public void writeSetBegin(TSet set
) throws TException
{
577 writeJSONArrayStart();
578 writeJSONString(getTypeNameForTypeID(set
.elemType
));
579 writeJSONInteger(set
.size
);
583 public void writeSetEnd() throws TException
{
588 public void writeBool(boolean b
) throws TException
{
589 writeJSONInteger(b ?
(long)1 : (long)0);
593 public void writeByte(byte b
) throws TException
{
594 writeJSONInteger((long)b
);
598 public void writeI16(short i16
) throws TException
{
599 writeJSONInteger((long)i16
);
603 public void writeI32(int i32
) throws TException
{
604 writeJSONInteger((long)i32
);
608 public void writeI64(long i64
) throws TException
{
609 writeJSONInteger(i64
);
613 public void writeDouble(double dub
) throws TException
{
614 writeJSONDouble(dub
);
618 public void writeString(String str
) throws TException
{
619 byte[] b
= str
.getBytes(StandardCharsets
.UTF_8
);
624 public void writeBinary(ByteBuffer bin
) throws TException
{
625 writeJSONBase64(bin
.array(), bin
.position() + bin
.arrayOffset(), bin
.limit() - bin
.position() - bin
.arrayOffset());
632 // Read in a JSON string, unescaping as appropriate.. Skip reading from the
633 // context if skipContext is true.
634 private TByteArrayOutputStream
readJSONString(boolean skipContext
)
636 TByteArrayOutputStream arr
= new TByteArrayOutputStream(DEF_STRING_SIZE
);
637 ArrayList
<Character
> codeunits
= new ArrayList
<Character
>();
641 readJSONSyntaxChar(QUOTE
);
643 byte ch
= reader_
.read();
644 if (ch
== QUOTE
[0]) {
647 if (ch
== ESCSEQ
[0]) {
649 if (ch
== ESCSEQ
[1]) {
650 trans_
.readAll(tmpbuf_
, 0, 4);
652 ((short)hexVal(tmpbuf_
[0]) << 12) +
653 ((short)hexVal(tmpbuf_
[1]) << 8) +
654 ((short)hexVal(tmpbuf_
[2]) << 4) +
655 (short)hexVal(tmpbuf_
[3]));
657 if (Character
.isHighSurrogate((char)cu
)) {
658 if (codeunits
.size() > 0) {
659 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
660 "Expected low surrogate char");
662 codeunits
.add((char)cu
);
664 else if (Character
.isLowSurrogate((char)cu
)) {
665 if (codeunits
.size() == 0) {
666 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
667 "Expected high surrogate char");
670 codeunits
.add((char)cu
);
672 (new String(new int[] { codeunits
.get(0), codeunits
.get(1) },
673 0, 2)).getBytes(StandardCharsets
.UTF_8
));
677 arr
.write((new String(new int[] { cu
}, 0, 1))
678 .getBytes(StandardCharsets
.UTF_8
));
681 } catch (IOException ex
) {
682 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
683 "Invalid unicode sequence");
687 int off
= ESCAPE_CHARS
.indexOf(ch
);
689 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
690 "Expected control char");
692 ch
= ESCAPE_CHAR_VALS
[off
];
700 // Return true if the given byte could be a valid part of a JSON number.
701 private boolean isJSONNumeric(byte b
) {
723 // Read in a sequence of characters that are all valid in JSON numbers. Does
724 // not do a complete regex check to validate that this is actually a number.
725 private String
readJSONNumericChars() throws TException
{
726 StringBuilder strbld
= new StringBuilder();
728 byte ch
= reader_
.peek();
729 if (!isJSONNumeric(ch
)) {
732 strbld
.append((char)reader_
.read());
734 return strbld
.toString();
737 // Read in a JSON number. If the context dictates, read in enclosing quotes.
738 private long readJSONInteger() throws TException
{
740 if (context_
.escapeNum()) {
741 readJSONSyntaxChar(QUOTE
);
743 String str
= readJSONNumericChars();
744 if (context_
.escapeNum()) {
745 readJSONSyntaxChar(QUOTE
);
748 return Long
.valueOf(str
);
750 catch (NumberFormatException ex
) {
751 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
752 "Bad data encounted in numeric data");
756 // Read in a JSON double value. Throw if the value is not wrapped in quotes
757 // when expected or if wrapped in quotes when not expected.
758 private double readJSONDouble() throws TException
{
760 if (reader_
.peek() == QUOTE
[0]) {
761 TByteArrayOutputStream arr
= readJSONString(true);
762 double dub
= Double
.valueOf(arr
.toString(StandardCharsets
.UTF_8
));
763 if (!context_
.escapeNum() && !Double
.isNaN(dub
)
764 && !Double
.isInfinite(dub
)) {
765 // Throw exception -- we should not be in a string in this case
766 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
767 "Numeric data unexpectedly quoted");
772 if (context_
.escapeNum()) {
773 // This will throw - we should have had a quote if escapeNum == true
774 readJSONSyntaxChar(QUOTE
);
777 return Double
.valueOf(readJSONNumericChars());
779 catch (NumberFormatException ex
) {
780 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
781 "Bad data encounted in numeric data");
786 // Read in a JSON string containing base-64 encoded data and decode it.
787 private byte[] readJSONBase64() throws TException
{
788 TByteArrayOutputStream arr
= readJSONString(false);
789 byte[] b
= arr
.get();
794 int bound
= len
>= 2 ? len
- 2 : 0;
795 for (int i
= len
- 1; i
>= bound
&& b
[i
] == '='; --i
) {
799 // Decode 4 bytes at a time
800 TBase64Utils
.decode(b
, off
, 4, b
, size
); // NB: decoded in place
805 // Don't decode if we hit the end or got a single leftover byte (invalid
806 // base64 but legal for skip of regular string type)
809 TBase64Utils
.decode(b
, off
, len
, b
, size
); // NB: decoded in place
812 // Sadly we must copy the byte[] (any way around this?)
813 byte [] result
= new byte[size
];
814 System
.arraycopy(b
, 0, result
, 0, size
);
818 private void readJSONObjectStart() throws TException
{
820 readJSONSyntaxChar(LBRACE
);
821 pushContext(new JSONPairContext());
824 private void readJSONObjectEnd() throws TException
{
825 readJSONSyntaxChar(RBRACE
);
829 private void readJSONArrayStart() throws TException
{
831 readJSONSyntaxChar(LBRACKET
);
832 pushContext(new JSONListContext());
835 private void readJSONArrayEnd() throws TException
{
836 readJSONSyntaxChar(RBRACKET
);
841 public TMessage
readMessageBegin() throws TException
{
842 resetContext(); // THRIFT-3743
843 readJSONArrayStart();
844 if (readJSONInteger() != VERSION
) {
845 throw new TProtocolException(TProtocolException
.BAD_VERSION
,
846 "Message contained bad version.");
848 String name
= readJSONString(false).toString(StandardCharsets
.UTF_8
);
849 byte type
= (byte) readJSONInteger();
850 int seqid
= (int) readJSONInteger();
851 return new TMessage(name
, type
, seqid
);
855 public void readMessageEnd() throws TException
{
860 public TStruct
readStructBegin() throws TException
{
861 readJSONObjectStart();
862 return ANONYMOUS_STRUCT
;
866 public void readStructEnd() throws TException
{
871 public TField
readFieldBegin() throws TException
{
872 byte ch
= reader_
.peek();
875 if (ch
== RBRACE
[0]) {
879 id
= (short) readJSONInteger();
880 readJSONObjectStart();
881 type
= getTypeIDForTypeName(readJSONString(false).get());
883 return new TField("", type
, id
);
887 public void readFieldEnd() throws TException
{
892 public TMap
readMapBegin() throws TException
{
893 readJSONArrayStart();
894 byte keyType
= getTypeIDForTypeName(readJSONString(false).get());
895 byte valueType
= getTypeIDForTypeName(readJSONString(false).get());
896 int size
= (int)readJSONInteger();
897 readJSONObjectStart();
898 return new TMap(keyType
, valueType
, size
);
902 public void readMapEnd() throws TException
{
908 public TList
readListBegin() throws TException
{
909 readJSONArrayStart();
910 byte elemType
= getTypeIDForTypeName(readJSONString(false).get());
911 int size
= (int)readJSONInteger();
912 return new TList(elemType
, size
);
916 public void readListEnd() throws TException
{
921 public TSet
readSetBegin() throws TException
{
922 readJSONArrayStart();
923 byte elemType
= getTypeIDForTypeName(readJSONString(false).get());
924 int size
= (int)readJSONInteger();
925 return new TSet(elemType
, size
);
929 public void readSetEnd() throws TException
{
934 public boolean readBool() throws TException
{
935 return (readJSONInteger() == 0 ?
false : true);
939 public byte readByte() throws TException
{
940 return (byte) readJSONInteger();
944 public short readI16() throws TException
{
945 return (short) readJSONInteger();
949 public int readI32() throws TException
{
950 return (int) readJSONInteger();
954 public long readI64() throws TException
{
955 return (long) readJSONInteger();
959 public double readDouble() throws TException
{
960 return readJSONDouble();
964 public String
readString() throws TException
{
965 return readJSONString(false).toString(StandardCharsets
.UTF_8
);
969 public ByteBuffer
readBinary() throws TException
{
970 return ByteBuffer
.wrap(readJSONBase64());