content: /* Copyright (C) 2016-2018 Alibaba Group Holding Limited
  
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
  
      http://www.apache.org/licenses/LICENSE-2.0
  
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
  ==============================================================================*/
  
  // Protocol Buffers - Google's data interchange format
  // Copyright 2008 Google Inc.  All rights reserved.
  // https://developers.google.com/protocol-buffers/
  //
  // Redistribution and use in source and binary forms, with or without
  // modification, are permitted provided that the following conditions are
  // met:
  //
  //     * Redistributions of source code must retain the above copyright
  // notice, this list of conditions and the following disclaimer.
  //     * Redistributions in binary form must reproduce the above
  // copyright notice, this list of conditions and the following disclaimer
  // in the documentation and/or other materials provided with the
  // distribution.
  //     * Neither the name of Google Inc. nor the names of its
  // contributors may be used to endorse or promote products derived from
  // this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  package com.google.protobuf.util;
  
  import com.google.protobuf.Any;
  import com.google.protobuf.BoolValue;
  import com.google.protobuf.ByteString;
  import com.google.protobuf.BytesValue;
  import com.google.protobuf.Descriptors.FieldDescriptor;
  import com.google.protobuf.DoubleValue;
  import com.google.protobuf.FloatValue;
  import com.google.protobuf.Int32Value;
  import com.google.protobuf.Int64Value;
  import com.google.protobuf.InvalidProtocolBufferException;
  import com.google.protobuf.ListValue;
  import com.google.protobuf.Message;
  import com.google.protobuf.NullValue;
  import com.google.protobuf.StringValue;
  import com.google.protobuf.Struct;
  import com.google.protobuf.UInt32Value;
  import com.google.protobuf.UInt64Value;
  import com.google.protobuf.Value;
  import com.google.protobuf.util.JsonFormat.TypeRegistry;
  import com.google.protobuf.util.JsonTestProto.TestAllTypes;
  import com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedEnum;
  import com.google.protobuf.util.JsonTestProto.TestAllTypes.NestedMessage;
  import com.google.protobuf.util.JsonTestProto.TestAny;
  import com.google.protobuf.util.JsonTestProto.TestCustomJsonName;
  import com.google.protobuf.util.JsonTestProto.TestDuration;
  import com.google.protobuf.util.JsonTestProto.TestFieldMask;
  import com.google.protobuf.util.JsonTestProto.TestMap;
  import com.google.protobuf.util.JsonTestProto.TestOneof;
  import com.google.protobuf.util.JsonTestProto.TestRecursive;
  import com.google.protobuf.util.JsonTestProto.TestStruct;
  import com.google.protobuf.util.JsonTestProto.TestTimestamp;
  import com.google.protobuf.util.JsonTestProto.TestWrappers;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.Reader;
  import java.io.StringReader;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Locale;
  import java.util.Set;
  import junit.framework.TestCase;
  
  public class JsonFormatTest extends TestCase {
    public JsonFormatTest() {
      // Test that locale does not affect JsonFormat.
      Locale.setDefault(Locale.forLanguageTag("hi-IN"));
    }
  
    private void setAllFields(TestAllTypes.Builder builder) {
      builder.setOptionalInt32(1234);
      builder.setOptionalInt64(1234567890123456789L);
      builder.setOptionalUint32(5678);
      builder.setOptionalUint64(2345678901234567890L);
      builder.setOptionalSint32(9012);
      builder.setOptionalSint64(3456789012345678901L);
      builder.setOptionalFixed32(3456);
      builder.setOptionalFixed64(4567890123456789012L);
      builder.setOptionalSfixed32(7890);
      builder.setOptionalSfixed64(5678901234567890123L);
      builder.setOptionalFloat(1.5f);
      builder.setOptionalDouble(1.25);
      builder.setOptionalBool(true);
      builder.setOptionalString("Hello world!");
      builder.setOptionalBytes(ByteString.copyFrom(new byte[] {0, 1, 2}));
      builder.setOptionalNestedEnum(NestedEnum.BAR);
      builder.getOptionalNestedMessageBuilder().setValue(100);
  
      builder.addRepeatedInt32(1234);
      builder.addRepeatedInt64(1234567890123456789L);
      builder.addRepeatedUint32(5678);
      builder.addRepeatedUint64(2345678901234567890L);
      builder.addRepeatedSint32(9012);
      builder.addRepeatedSint64(3456789012345678901L);
      builder.addRepeatedFixed32(3456);
      builder.addRepeatedFixed64(4567890123456789012L);
      builder.addRepeatedSfixed32(7890);
      builder.addRepeatedSfixed64(5678901234567890123L);
      builder.addRepeatedFloat(1.5f);
      builder.addRepeatedDouble(1.25);
      builder.addRepeatedBool(true);
      builder.addRepeatedString("Hello world!");
      builder.addRepeatedBytes(ByteString.copyFrom(new byte[] {0, 1, 2}));
      builder.addRepeatedNestedEnum(NestedEnum.BAR);
      builder.addRepeatedNestedMessageBuilder().setValue(100);
  
      builder.addRepeatedInt32(234);
      builder.addRepeatedInt64(234567890123456789L);
      builder.addRepeatedUint32(678);
      builder.addRepeatedUint64(345678901234567890L);
      builder.addRepeatedSint32(012);
      builder.addRepeatedSint64(456789012345678901L);
      builder.addRepeatedFixed32(456);
      builder.addRepeatedFixed64(567890123456789012L);
      builder.addRepeatedSfixed32(890);
      builder.addRepeatedSfixed64(678901234567890123L);
      builder.addRepeatedFloat(11.5f);
      builder.addRepeatedDouble(11.25);
      builder.addRepeatedBool(true);
      builder.addRepeatedString("ello world!");
      builder.addRepeatedBytes(ByteString.copyFrom(new byte[] {1, 2}));
      builder.addRepeatedNestedEnum(NestedEnum.BAZ);
      builder.addRepeatedNestedMessageBuilder().setValue(200);
    }
  
    private void assertRoundTripEquals(Message message) throws Exception {
      assertRoundTripEquals(message, TypeRegistry.getEmptyTypeRegistry());
    }
  
    private void assertRoundTripEquals(Message message, TypeRegistry registry) throws Exception {
      JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry);
      JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(registry);
      Message.Builder builder = message.newBuilderForType();
      parser.merge(printer.print(message), builder);
      Message parsedMessage = builder.build();
      assertEquals(message.toString(), parsedMessage.toString());
    }
  
    private String toJsonString(Message message) throws IOException {
      return JsonFormat.printer().print(message);
    }
    private String toCompactJsonString(Message message) throws IOException {
      return JsonFormat.printer().omittingInsignificantWhitespace().print(message);
    }
  
    private void mergeFromJson(String json, Message.Builder builder) throws IOException {
      JsonFormat.parser().merge(json, builder);
    }
  
    public void testAllFields() throws Exception {
      TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      setAllFields(builder);
      TestAllTypes message = builder.build();
  
      assertEquals(
          "{\n"
              + "  \"optionalInt32\": 1234,\n"
              + "  \"optionalInt64\": \"1234567890123456789\",\n"
              + "  \"optionalUint32\": 5678,\n"
              + "  \"optionalUint64\": \"2345678901234567890\",\n"
              + "  \"optionalSint32\": 9012,\n"
              + "  \"optionalSint64\": \"3456789012345678901\",\n"
              + "  \"optionalFixed32\": 3456,\n"
              + "  \"optionalFixed64\": \"4567890123456789012\",\n"
              + "  \"optionalSfixed32\": 7890,\n"
              + "  \"optionalSfixed64\": \"5678901234567890123\",\n"
              + "  \"optionalFloat\": 1.5,\n"
              + "  \"optionalDouble\": 1.25,\n"
              + "  \"optionalBool\": true,\n"
              + "  \"optionalString\": \"Hello world!\",\n"
              + "  \"optionalBytes\": \"AAEC\",\n"
              + "  \"optionalNestedMessage\": {\n"
              + "    \"value\": 100\n"
              + "  },\n"
              + "  \"optionalNestedEnum\": \"BAR\",\n"
              + "  \"repeatedInt32\": [1234, 234],\n"
              + "  \"repeatedInt64\": [\"1234567890123456789\", \"234567890123456789\"],\n"
              + "  \"repeatedUint32\": [5678, 678],\n"
              + "  \"repeatedUint64\": [\"2345678901234567890\", \"345678901234567890\"],\n"
              + "  \"repeatedSint32\": [9012, 10],\n"
              + "  \"repeatedSint64\": [\"3456789012345678901\", \"456789012345678901\"],\n"
              + "  \"repeatedFixed32\": [3456, 456],\n"
              + "  \"repeatedFixed64\": [\"4567890123456789012\", \"567890123456789012\"],\n"
              + "  \"repeatedSfixed32\": [7890, 890],\n"
              + "  \"repeatedSfixed64\": [\"5678901234567890123\", \"678901234567890123\"],\n"
              + "  \"repeatedFloat\": [1.5, 11.5],\n"
              + "  \"repeatedDouble\": [1.25, 11.25],\n"
              + "  \"repeatedBool\": [true, true],\n"
              + "  \"repeatedString\": [\"Hello world!\", \"ello world!\"],\n"
              + "  \"repeatedBytes\": [\"AAEC\", \"AQI=\"],\n"
              + "  \"repeatedNestedMessage\": [{\n"
              + "    \"value\": 100\n"
              + "  }, {\n"
              + "    \"value\": 200\n"
              + "  }],\n"
              + "  \"repeatedNestedEnum\": [\"BAR\", \"BAZ\"]\n"
              + "}",
          toJsonString(message));
  
      assertRoundTripEquals(message);
    }
  
    public void testUnknownEnumValues() throws Exception {
      TestAllTypes message =
          TestAllTypes.newBuilder()
              .setOptionalNestedEnumValue(12345)
              .addRepeatedNestedEnumValue(12345)
              .addRepeatedNestedEnumValue(0)
              .build();
      assertEquals(
          "{\n"
              + "  \"optionalNestedEnum\": 12345,\n"
              + "  \"repeatedNestedEnum\": [12345, \"FOO\"]\n"
              + "}",
          toJsonString(message));
      assertRoundTripEquals(message);
  
      TestMap.Builder mapBuilder = TestMap.newBuilder();
      mapBuilder.putInt32ToEnumMapValue(1, 0);
      mapBuilder.putInt32ToEnumMapValue(2, 12345);
      TestMap mapMessage = mapBuilder.build();
      assertEquals(
          "{\n"
              + "  \"int32ToEnumMap\": {\n"
              + "    \"1\": \"FOO\",\n"
              + "    \"2\": 12345\n"
              + "  }\n"
              + "}",
          toJsonString(mapMessage));
      assertRoundTripEquals(mapMessage);
    }
  
    public void testSpecialFloatValues() throws Exception {
      TestAllTypes message =
          TestAllTypes.newBuilder()
              .addRepeatedFloat(Float.NaN)
              .addRepeatedFloat(Float.POSITIVE_INFINITY)
              .addRepeatedFloat(Float.NEGATIVE_INFINITY)
              .addRepeatedDouble(Double.NaN)
              .addRepeatedDouble(Double.POSITIVE_INFINITY)
              .addRepeatedDouble(Double.NEGATIVE_INFINITY)
              .build();
      assertEquals(
          "{\n"
              + "  \"repeatedFloat\": [\"NaN\", \"Infinity\", \"-Infinity\"],\n"
              + "  \"repeatedDouble\": [\"NaN\", \"Infinity\", \"-Infinity\"]\n"
              + "}",
          toJsonString(message));
  
      assertRoundTripEquals(message);
    }
  
    public void testParserAcceptStringForNumericField() throws Exception {
      TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      mergeFromJson(
          "{\n"
              + "  \"optionalInt32\": \"1234\",\n"
              + "  \"optionalUint32\": \"5678\",\n"
              + "  \"optionalSint32\": \"9012\",\n"
              + "  \"optionalFixed32\": \"3456\",\n"
              + "  \"optionalSfixed32\": \"7890\",\n"
              + "  \"optionalFloat\": \"1.5\",\n"
              + "  \"optionalDouble\": \"1.25\",\n"
              + "  \"optionalBool\": \"true\"\n"
              + "}",
          builder);
      TestAllTypes message = builder.build();
      assertEquals(1234, message.getOptionalInt32());
      assertEquals(5678, message.getOptionalUint32());
      assertEquals(9012, message.getOptionalSint32());
      assertEquals(3456, message.getOptionalFixed32());
      assertEquals(7890, message.getOptionalSfixed32());
      assertEquals(1.5f, message.getOptionalFloat(), 0.0f);
      assertEquals(1.25, message.getOptionalDouble(), 0.0);
      assertEquals(true, message.getOptionalBool());
    }
  
    public void testParserAcceptFloatingPointValueForIntegerField() throws Exception {
      // Test that numeric values like "1.000", "1e5" will also be accepted.
      TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      mergeFromJson(
          "{\n"
              + "  \"repeatedInt32\": [1.000, 1e5, \"1.000\", \"1e5\"],\n"
              + "  \"repeatedUint32\": [1.000, 1e5, \"1.000\", \"1e5\"],\n"
              + "  \"repeatedInt64\": [1.000, 1e5, \"1.000\", \"1e5\"],\n"
              + "  \"repeatedUint64\": [1.000, 1e5, \"1.000\", \"1e5\"]\n"
              + "}",
          builder);
      int[] expectedValues = new int[] {1, 100000, 1, 100000};
      assertEquals(4, builder.getRepeatedInt32Count());
      assertEquals(4, builder.getRepeatedUint32Count());
      assertEquals(4, builder.getRepeatedInt64Count());
      assertEquals(4, builder.getRepeatedUint64Count());
      for (int i = 0; i < 4; ++i) {
        assertEquals(expectedValues[i], builder.getRepeatedInt32(i));
        assertEquals(expectedValues[i], builder.getRepeatedUint32(i));
        assertEquals(expectedValues[i], builder.getRepeatedInt64(i));
        assertEquals(expectedValues[i], builder.getRepeatedUint64(i));
      }
  
      // Non-integers will still be rejected.
      assertRejects("optionalInt32", "1.5");
      assertRejects("optionalUint32", "1.5");
      assertRejects("optionalInt64", "1.5");
      assertRejects("optionalUint64", "1.5");
    }
  
    private void assertRejects(String name, String value) {
      TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      try {
        // Numeric form is rejected.
        mergeFromJson("{\"" + name + "\":" + value + "}", builder);
        fail("Exception is expected.");
      } catch (IOException e) {
        // Expected.
      }
      try {
        // String form is also rejected.
        mergeFromJson("{\"" + name + "\":\"" + value + "\"}", builder);
        fail("Exception is expected.");
      } catch (IOException e) {
        // Expected.
      }
    }
  
    private void assertAccepts(String name, String value) throws IOException {
      TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      // Both numeric form and string form are accepted.
      mergeFromJson("{\"" + name + "\":" + value + "}", builder);
      builder.clear();
      mergeFromJson("{\"" + name + "\":\"" + value + "\"}", builder);
    }
  
    public void testParserRejectOutOfRangeNumericValues() throws Exception {
      assertAccepts("optionalInt32", String.valueOf(Integer.MAX_VALUE));
      assertAccepts("optionalInt32", String.valueOf(Integer.MIN_VALUE));
      assertRejects("optionalInt32", String.valueOf(Integer.MAX_VALUE + 1L));
      assertRejects("optionalInt32", String.valueOf(Integer.MIN_VALUE - 1L));
  
      assertAccepts("optionalUint32", String.valueOf(Integer.MAX_VALUE + 1L));
      assertRejects("optionalUint32", "123456789012345");
      assertRejects("optionalUint32", "-1");
  
      BigInteger one = new BigInteger("1");
      BigInteger maxLong = new BigInteger(String.valueOf(Long.MAX_VALUE));
      BigInteger minLong = new BigInteger(String.valueOf(Long.MIN_VALUE));
      assertAccepts("optionalInt64", maxLong.toString());
      assertAccepts("optionalInt64", minLong.toString());
      assertRejects("optionalInt64", maxLong.add(one).toString());
      assertRejects("optionalInt64", minLong.subtract(one).toString());
  
      assertAccepts("optionalUint64", maxLong.add(one).toString());
      assertRejects("optionalUint64", "1234567890123456789012345");
      assertRejects("optionalUint64", "-1");
  
      assertAccepts("optionalBool", "true");
      assertRejects("optionalBool", "1");
      assertRejects("optionalBool", "0");
  
      assertAccepts("optionalFloat", String.valueOf(Float.MAX_VALUE));
      assertAccepts("optionalFloat", String.valueOf(-Float.MAX_VALUE));
      assertRejects("optionalFloat", String.valueOf(Double.MAX_VALUE));
      assertRejects("optionalFloat", String.valueOf(-Double.MAX_VALUE));
  
      BigDecimal moreThanOne = new BigDecimal("1.000001");
      BigDecimal maxDouble = new BigDecimal(Double.MAX_VALUE);
      BigDecimal minDouble = new BigDecimal(-Double.MAX_VALUE);
      assertAccepts("optionalDouble", maxDouble.toString());
      assertAccepts("optionalDouble", minDouble.toString());
      assertRejects("optionalDouble", maxDouble.multiply(moreThanOne).toString());
      assertRejects("optionalDouble", minDouble.multiply(moreThanOne).toString());
    }
  
    public void testParserAcceptNull() throws Exception {
      TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      mergeFromJson(
          "{\n"
              + "  \"optionalInt32\": null,\n"
              + "  \"optionalInt64\": null,\n"
              + "  \"optionalUint32\": null,\n"
              + "  \"optionalUint64\": null,\n"
              + "  \"optionalSint32\": null,\n"
              + "  \"optionalSint64\": null,\n"
              + "  \"optionalFixed32\": null,\n"
              + "  \"optionalFixed64\": null,\n"
              + "  \"optionalSfixed32\": null,\n"
              + "  \"optionalSfixed64\": null,\n"
              + "  \"optionalFloat\": null,\n"
              + "  \"optionalDouble\": null,\n"
              + "  \"optionalBool\": null,\n"
              + "  \"optionalString\": null,\n"
              + "  \"optionalBytes\": null,\n"
              + "  \"optionalNestedMessage\": null,\n"
              + "  \"optionalNestedEnum\": null,\n"
              + "  \"repeatedInt32\": null,\n"
              + "  \"repeatedInt64\": null,\n"
              + "  \"repeatedUint32\": null,\n"
              + "  \"repeatedUint64\": null,\n"
              + "  \"repeatedSint32\": null,\n"
              + "  \"repeatedSint64\": null,\n"
              + "  \"repeatedFixed32\": null,\n"
              + "  \"repeatedFixed64\": null,\n"
              + "  \"repeatedSfixed32\": null,\n"
              + "  \"repeatedSfixed64\": null,\n"
              + "  \"repeatedFloat\": null,\n"
              + "  \"repeatedDouble\": null,\n"
              + "  \"repeatedBool\": null,\n"
              + "  \"repeatedString\": null,\n"
              + "  \"repeatedBytes\": null,\n"
              + "  \"repeatedNestedMessage\": null,\n"
              + "  \"repeatedNestedEnum\": null\n"
              + "}",
          builder);
      TestAllTypes message = builder.build();
      assertEquals(TestAllTypes.getDefaultInstance(), message);
  
      // Repeated field elements cannot be null.
      try {
        builder = TestAllTypes.newBuilder();
        mergeFromJson("{\n" + "  \"repeatedInt32\": [null, null],\n" + "}", builder);
        fail();
      } catch (InvalidProtocolBufferException e) {
        // Exception expected.
      }
  
      try {
        builder = TestAllTypes.newBuilder();
        mergeFromJson("{\n" + "  \"repeatedNestedMessage\": [null, null],\n" + "}", builder);
        fail();
      } catch (InvalidProtocolBufferException e) {
        // Exception expected.
      }
    }
  
    public void testNullInOneof() throws Exception {
      TestOneof.Builder builder = TestOneof.newBuilder();
      mergeFromJson("{\n" + "  \"oneofNullValue\": null \n" + "}", builder);
      TestOneof message = builder.build();
      assertEquals(TestOneof.OneofFieldCase.ONEOF_NULL_VALUE, message.getOneofFieldCase());
      assertEquals(NullValue.NULL_VALUE, message.getOneofNullValue());
    }
  
    public void testParserRejectDuplicatedFields() throws Exception {
      // TODO(xiaofeng): The parser we are currently using (GSON) will accept and keep the last
      // one if multiple entries have the same name. This is not the desired behavior but it can
      // only be fixed by using our own parser. Here we only test the cases where the names are
      // different but still referring to the same field.
  
      // Duplicated optional fields.
      try {
        TestAllTypes.Builder builder = TestAllTypes.newBuilder();
        mergeFromJson(
            "{\n"
                + "  \"optionalNestedMessage\": {},\n"
                + "  \"optional_nested_message\": {}\n"
                + "}",
            builder);
        fail();
      } catch (InvalidProtocolBufferException e) {
        // Exception expected.
      }
  
      // Duplicated repeated fields.
      try {
        TestAllTypes.Builder builder = TestAllTypes.newBuilder();
        mergeFromJson(
            "{\n"
                + "  \"repeatedInt32\": [1, 2],\n"
                + "  \"repeated_int32\": [5, 6]\n"
                + "}",
            builder);
        fail();
      } catch (InvalidProtocolBufferException e) {
        // Exception expected.
      }
  
      // Duplicated oneof fields, same name.
      try {
        TestOneof.Builder builder = TestOneof.newBuilder();
        mergeFromJson("{\n" + "  \"oneofInt32\": 1,\n" + "  \"oneof_int32\": 2\n" + "}", builder);
        fail();
      } catch (InvalidProtocolBufferException e) {
        // Exception expected.
      }
  
      // Duplicated oneof fields, different name.
      try {
        TestOneof.Builder builder = TestOneof.newBuilder();
        mergeFromJson(
            "{\n" + "  \"oneofInt32\": 1,\n" + "  \"oneofNullValue\": null\n" + "}", builder);
        fail();
      } catch (InvalidProtocolBufferException e) {
        // Exception expected.
      }
    }
  
    public void testMapFields() throws Exception {
      TestMap.Builder builder = TestMap.newBuilder();
      builder.putInt32ToInt32Map(1, 10);
      builder.putInt64ToInt32Map(1234567890123456789L, 10);
      builder.putUint32ToInt32Map(2, 20);
      builder.putUint64ToInt32Map(2234567890123456789L, 20);
      builder.putSint32ToInt32Map(3, 30);
      builder.putSint64ToInt32Map(3234567890123456789L, 30);
      builder.putFixed32ToInt32Map(4, 40);
      builder.putFixed64ToInt32Map(4234567890123456789L, 40);
      builder.putSfixed32ToInt32Map(5, 50);
      builder.putSfixed64ToInt32Map(5234567890123456789L, 50);
      builder.putBoolToInt32Map(false, 6);
      builder.putStringToInt32Map("Hello", 10);
  
      builder.putInt32ToInt64Map(1, 1234567890123456789L);
      builder.putInt32ToUint32Map(2, 20);
      builder.putInt32ToUint64Map(2, 2234567890123456789L);
      builder.putInt32ToSint32Map(3, 30);
      builder.putInt32ToSint64Map(3, 3234567890123456789L);
      builder.putInt32ToFixed32Map(4, 40);
      builder.putInt32ToFixed64Map(4, 4234567890123456789L);
      builder.putInt32ToSfixed32Map(5, 50);
      builder.putInt32ToSfixed64Map(5, 5234567890123456789L);
      builder.putInt32ToFloatMap(6, 1.5f);
      builder.putInt32ToDoubleMap(6, 1.25);
      builder.putInt32ToBoolMap(7, false);
      builder.putInt32ToStringMap(7, "World");
      builder.putInt32ToBytesMap(8, ByteString.copyFrom(new byte[] {1, 2, 3}));
      builder.putInt32ToMessageMap(8, NestedMessage.newBuilder().setValue(1234).build());
      builder.putInt32ToEnumMap(9, NestedEnum.BAR);
      TestMap message = builder.build();
  
      assertEquals(
          "{\n"
              + "  \"int32ToInt32Map\": {\n"
              + "    \"1\": 10\n"
              + "  },\n"
              + "  \"int64ToInt32Map\": {\n"
              + "    \"1234567890123456789\": 10\n"
              + "  },\n"
              + "  \"uint32ToInt32Map\": {\n"
              + "    \"2\": 20\n"
              + "  },\n"
              + "  \"uint64ToInt32Map\": {\n"
              + "    \"2234567890123456789\": 20\n"
              + "  },\n"
              + "  \"sint32ToInt32Map\": {\n"
              + "    \"3\": 30\n"
              + "  },\n"
              + "  \"sint64ToInt32Map\": {\n"
              + "    \"3234567890123456789\": 30\n"
              + "  },\n"
              + "  \"fixed32ToInt32Map\": {\n"
              + "    \"4\": 40\n"
              + "  },\n"
              + "  \"fixed64ToInt32Map\": {\n"
              + "    \"4234567890123456789\": 40\n"
              + "  },\n"
              + "  \"sfixed32ToInt32Map\": {\n"
              + "    \"5\": 50\n"
              + "  },\n"
              + "  \"sfixed64ToInt32Map\": {\n"
              + "    \"5234567890123456789\": 50\n"
              + "  },\n"
              + "  \"boolToInt32Map\": {\n"
              + "    \"false\": 6\n"
              + "  },\n"
              + "  \"stringToInt32Map\": {\n"
              + "    \"Hello\": 10\n"
              + "  },\n"
              + "  \"int32ToInt64Map\": {\n"
              + "    \"1\": \"1234567890123456789\"\n"
              + "  },\n"
              + "  \"int32ToUint32Map\": {\n"
              + "    \"2\": 20\n"
              + "  },\n"
              + "  \"int32ToUint64Map\": {\n"
              + "    \"2\": \"2234567890123456789\"\n"
              + "  },\n"
              + "  \"int32ToSint32Map\": {\n"
              + "    \"3\": 30\n"
              + "  },\n"
              + "  \"int32ToSint64Map\": {\n"
              + "    \"3\": \"3234567890123456789\"\n"
              + "  },\n"
              + "  \"int32ToFixed32Map\": {\n"
              + "    \"4\": 40\n"
              + "  },\n"
              + "  \"int32ToFixed64Map\": {\n"
              + "    \"4\": \"4234567890123456789\"\n"
              + "  },\n"
              + "  \"int32ToSfixed32Map\": {\n"
              + "    \"5\": 50\n"
              + "  },\n"
              + "  \"int32ToSfixed64Map\": {\n"
              + "    \"5\": \"5234567890123456789\"\n"
              + "  },\n"
              + "  \"int32ToFloatMap\": {\n"
              + "    \"6\": 1.5\n"
              + "  },\n"
              + "  \"int32ToDoubleMap\": {\n"
              + "    \"6\": 1.25\n"
              + "  },\n"
              + "  \"int32ToBoolMap\": {\n"
              + "    \"7\": false\n"
              + "  },\n"
              + "  \"int32ToStringMap\": {\n"
              + "    \"7\": \"World\"\n"
              + "  },\n"
              + "  \"int32ToBytesMap\": {\n"
              + "    \"8\": \"AQID\"\n"
              + "  },\n"
              + "  \"int32ToMessageMap\": {\n"
              + "    \"8\": {\n"
              + "      \"value\": 1234\n"
              + "    }\n"
              + "  },\n"
              + "  \"int32ToEnumMap\": {\n"
              + "    \"9\": \"BAR\"\n"
              + "  }\n"
              + "}",
          toJsonString(message));
      assertRoundTripEquals(message);
  
      // Test multiple entries.
      builder = TestMap.newBuilder();
      builder.putInt32ToInt32Map(1, 2);
      builder.putInt32ToInt32Map(3, 4);
      message = builder.build();
  
      assertEquals(
          "{\n" + "  \"int32ToInt32Map\": {\n" + "    \"1\": 2,\n" + "    \"3\": 4\n" + "  }\n" + "}",
          toJsonString(message));
      assertRoundTripEquals(message);
    }
  
    public void testMapNullValueIsRejected() throws Exception {
      try {
        TestMap.Builder builder = TestMap.newBuilder();
        mergeFromJson(
            "{\n"
                + "  \"int32ToInt32Map\": {null: 1},\n"
                + "  \"int32ToMessageMap\": {null: 2}\n"
                + "}",
            builder);
        fail();
      } catch (InvalidProtocolBufferException e) {
        // Exception expected.
      }
  
      try {
        TestMap.Builder builder = TestMap.newBuilder();
        mergeFromJson(
            "{\n"
                + "  \"int32ToInt32Map\": {\"1\": null},\n"
                + "  \"int32ToMessageMap\": {\"2\": null}\n"
                + "}",
            builder);
        fail();
      } catch (InvalidProtocolBufferException e) {
        // Exception expected.
      }
    }
  
    public void testParserAcceptNonQuotedObjectKey() throws Exception {
      TestMap.Builder builder = TestMap.newBuilder();
      mergeFromJson(
          "{\n" + "  int32ToInt32Map: {1: 2},\n" + "  stringToInt32Map: {hello: 3}\n" + "}", builder);
      TestMap message = builder.build();
      assertEquals(2, message.getInt32ToInt32Map().get(1).intValue());
      assertEquals(3, message.getStringToInt32Map().get("hello").intValue());
    }
  
    public void testWrappers() throws Exception {
      TestWrappers.Builder builder = TestWrappers.newBuilder();
      builder.getBoolValueBuilder().setValue(false);
      builder.getInt32ValueBuilder().setValue(0);
      builder.getInt64ValueBuilder().setValue(0);
      builder.getUint32ValueBuilder().setValue(0);
      builder.getUint64ValueBuilder().setValue(0);
      builder.getFloatValueBuilder().setValue(0.0f);
      builder.getDoubleValueBuilder().setValue(0.0);
      builder.getStringValueBuilder().setValue("");
      builder.getBytesValueBuilder().setValue(ByteString.EMPTY);
      TestWrappers message = builder.build();
  
      assertEquals(
          "{\n"
              + "  \"int32Value\": 0,\n"
              + "  \"uint32Value\": 0,\n"
              + "  \"int64Value\": \"0\",\n"
              + "  \"uint64Value\": \"0\",\n"
              + "  \"floatValue\": 0.0,\n"
              + "  \"doubleValue\": 0.0,\n"
              + "  \"boolValue\": false,\n"
              + "  \"stringValue\": \"\",\n"
              + "  \"bytesValue\": \"\"\n"
              + "}",
          toJsonString(message));
      assertRoundTripEquals(message);
  
      builder = TestWrappers.newBuilder();
      builder.getBoolValueBuilder().setValue(true);
      builder.getInt32ValueBuilder().setValue(1);
      builder.getInt64ValueBuilder().setValue(2);
      builder.getUint32ValueBuilder().setValue(3);
      builder.getUint64ValueBuilder().setValue(4);
      builder.getFloatValueBuilder().setValue(5.0f);
      builder.getDoubleValueBuilder().setValue(6.0);
      builder.getStringValueBuilder().setValue("7");
      builder.getBytesValueBuilder().setValue(ByteString.copyFrom(new byte[] {8}));
      message = builder.build();
  
      assertEquals(
          "{\n"
              + "  \"int32Value\": 1,\n"
              + "  \"uint32Value\": 3,\n"
              + "  \"int64Value\": \"2\",\n"
              + "  \"uint64Value\": \"4\",\n"
              + "  \"floatValue\": 5.0,\n"
              + "  \"doubleValue\": 6.0,\n"
              + "  \"boolValue\": true,\n"
              + "  \"stringValue\": \"7\",\n"
              + "  \"bytesValue\": \"CA==\"\n"
              + "}",
          toJsonString(message));
      assertRoundTripEquals(message);
    }
  
    public void testTimestamp() throws Exception {
      TestTimestamp message =
          TestTimestamp.newBuilder()
              .setTimestampValue(Timestamps.parse("1970-01-01T00:00:00Z"))
              .build();
  
      assertEquals(
          "{\n" + "  \"timestampValue\": \"1970-01-01T00:00:00Z\"\n" + "}", toJsonString(message));
      assertRoundTripEquals(message);
    }
  
    public void testDuration() throws Exception {
      TestDuration message =
          TestDuration.newBuilder().setDurationValue(Durations.parse("12345s")).build();
  
      assertEquals("{\n" + "  \"durationValue\": \"12345s\"\n" + "}", toJsonString(message));
      assertRoundTripEquals(message);
    }
  
    public void testFieldMask() throws Exception {
      TestFieldMask message =
          TestFieldMask.newBuilder()
              .setFieldMaskValue(FieldMaskUtil.fromString("foo.bar,baz,foo_bar.baz"))
              .build();
  
      assertEquals(
          "{\n" + "  \"fieldMaskValue\": \"foo.bar,baz,fooBar.baz\"\n" + "}", toJsonString(message));
      assertRoundTripEquals(message);
    }
  
    public void testStruct() throws Exception {
      // Build a struct with all possible values.
      TestStruct.Builder builder = TestStruct.newBuilder();
      Struct.Builder structBuilder = builder.getStructValueBuilder();
      structBuilder.putFields("null_value", Value.newBuilder().setNullValueValue(0).build());
      structBuilder.putFields("number_value", Value.newBuilder().setNumberValue(1.25).build());
      structBuilder.putFields("string_value", Value.newBuilder().setStringValue("hello").build());
      Struct.Builder subStructBuilder = Struct.newBuilder();
      subStructBuilder.putFields("number_value", Value.newBuilder().setNumberValue(1234).build());
      structBuilder.putFields(
          "struct_value", Value.newBuilder().setStructValue(subStructBuilder.build()).build());
      ListValue.Builder listBuilder = ListValue.newBuilder();
      listBuilder.addValues(Value.newBuilder().setNumberValue(1.125).build());
      listBuilder.addValues(Value.newBuilder().setNullValueValue(0).build());
      structBuilder.putFields(
          "list_value", Value.newBuilder().setListValue(listBuilder.build()).build());
      TestStruct message = builder.build();
  
      assertEquals(
          "{\n"
              + "  \"structValue\": {\n"
              + "    \"null_value\": null,\n"
              + "    \"number_value\": 1.25,\n"
              + "    \"string_value\": \"hello\",\n"
              + "    \"struct_value\": {\n"
              + "      \"number_value\": 1234.0\n"
              + "    },\n"
              + "    \"list_value\": [1.125, null]\n"
              + "  }\n"
              + "}",
          toJsonString(message));
      assertRoundTripEquals(message);
  
      builder = TestStruct.newBuilder();
      builder.setValue(Value.newBuilder().setNullValueValue(0).build());
      message = builder.build();
      assertEquals("{\n" + "  \"value\": null\n" + "}", toJsonString(message));
      assertRoundTripEquals(message);
  
      builder = TestStruct.newBuilder();
      listBuilder = builder.getListValueBuilder();
      listBuilder.addValues(Value.newBuilder().setNumberValue(31831.125).build());
      listBuilder.addValues(Value.newBuilder().setNullValueValue(0).build());
      message = builder.build();
      assertEquals("{\n" + "  \"listValue\": [31831.125, null]\n" + "}", toJsonString(message));
      assertRoundTripEquals(message);
    }
  
    public void testAnyFields() throws Exception {
      TestAllTypes content = TestAllTypes.newBuilder().setOptionalInt32(1234).build();
      TestAny message = TestAny.newBuilder().setAnyValue(Any.pack(content)).build();
  
      // A TypeRegistry must be provided in order to convert Any types.
      try {
        toJsonString(message);
        fail("Exception is expected.");
      } catch (IOException e) {
        // Expected.
      }
  
      JsonFormat.TypeRegistry registry =
          JsonFormat.TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build();
      JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry);
  
      assertEquals(
          "{\n"
              + "  \"anyValue\": {\n"
              + "    \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n"
              + "    \"optionalInt32\": 1234\n"
              + "  }\n"
              + "}",
          printer.print(message));
      assertRoundTripEquals(message, registry);
  
      TestAny messageWithDefaultAnyValue =
          TestAny.newBuilder().setAnyValue(Any.getDefaultInstance()).build();
      assertEquals(
          "{\n"
              + "  \"anyValue\": {}\n"
              + "}",
          printer.print(messageWithDefaultAnyValue));
      assertRoundTripEquals(messageWithDefaultAnyValue, registry);
  
      // Well-known types have a special formatting when embedded in Any.
      //
      // 1. Any in Any.
      Any anyMessage = Any.pack(Any.pack(content));
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n"
              + "  \"value\": {\n"
              + "    \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n"
              + "    \"optionalInt32\": 1234\n"
              + "  }\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
  
      // 2. Wrappers in Any.
      anyMessage = Any.pack(Int32Value.newBuilder().setValue(12345).build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.Int32Value\",\n"
              + "  \"value\": 12345\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
      anyMessage = Any.pack(UInt32Value.newBuilder().setValue(12345).build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.UInt32Value\",\n"
              + "  \"value\": 12345\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
      anyMessage = Any.pack(Int64Value.newBuilder().setValue(12345).build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.Int64Value\",\n"
              + "  \"value\": \"12345\"\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
      anyMessage = Any.pack(UInt64Value.newBuilder().setValue(12345).build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.UInt64Value\",\n"
              + "  \"value\": \"12345\"\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
      anyMessage = Any.pack(FloatValue.newBuilder().setValue(12345).build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.FloatValue\",\n"
              + "  \"value\": 12345.0\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
      anyMessage = Any.pack(DoubleValue.newBuilder().setValue(12345).build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.DoubleValue\",\n"
              + "  \"value\": 12345.0\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
      anyMessage = Any.pack(BoolValue.newBuilder().setValue(true).build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.BoolValue\",\n"
              + "  \"value\": true\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
      anyMessage = Any.pack(StringValue.newBuilder().setValue("Hello").build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.StringValue\",\n"
              + "  \"value\": \"Hello\"\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
      anyMessage =
          Any.pack(BytesValue.newBuilder().setValue(ByteString.copyFrom(new byte[] {1, 2})).build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.BytesValue\",\n"
              + "  \"value\": \"AQI=\"\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
  
      // 3. Timestamp in Any.
      anyMessage = Any.pack(Timestamps.parse("1969-12-31T23:59:59Z"));
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\",\n"
              + "  \"value\": \"1969-12-31T23:59:59Z\"\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
  
      // 4. Duration in Any
      anyMessage = Any.pack(Durations.parse("12345.10s"));
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n"
              + "  \"value\": \"12345.100s\"\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
  
      // 5. FieldMask in Any
      anyMessage = Any.pack(FieldMaskUtil.fromString("foo.bar,baz"));
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.FieldMask\",\n"
              + "  \"value\": \"foo.bar,baz\"\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
  
      // 6. Struct in Any
      Struct.Builder structBuilder = Struct.newBuilder();
      structBuilder.putFields("number", Value.newBuilder().setNumberValue(1.125).build());
      anyMessage = Any.pack(structBuilder.build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.Struct\",\n"
              + "  \"value\": {\n"
              + "    \"number\": 1.125\n"
              + "  }\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
  
      // 7. Value (number type) in Any
      Value.Builder valueBuilder = Value.newBuilder();
      valueBuilder.setNumberValue(1);
      anyMessage = Any.pack(valueBuilder.build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n"
              + "  \"value\": 1.0\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
  
      // 8. Value (null type) in Any
      anyMessage = Any.pack(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build());
      assertEquals(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n"
              + "  \"value\": null\n"
              + "}",
          printer.print(anyMessage));
      assertRoundTripEquals(anyMessage, registry);
    }
  
    public void testAnyInMaps() throws Exception {
      JsonFormat.TypeRegistry registry =
          JsonFormat.TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build();
      JsonFormat.Printer printer = JsonFormat.printer().usingTypeRegistry(registry);
  
      TestAny.Builder testAny = TestAny.newBuilder();
      testAny.putAnyMap("int32_wrapper", Any.pack(Int32Value.newBuilder().setValue(123).build()));
      testAny.putAnyMap("int64_wrapper", Any.pack(Int64Value.newBuilder().setValue(456).build()));
      testAny.putAnyMap("timestamp", Any.pack(Timestamps.parse("1969-12-31T23:59:59Z")));
      testAny.putAnyMap("duration", Any.pack(Durations.parse("12345.1s")));
      testAny.putAnyMap("field_mask", Any.pack(FieldMaskUtil.fromString("foo.bar,baz")));
      Value numberValue = Value.newBuilder().setNumberValue(1.125).build();
      Struct.Builder struct = Struct.newBuilder();
      struct.putFields("number", numberValue);
      testAny.putAnyMap("struct", Any.pack(struct.build()));
      Value nullValue = Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build();
      testAny.putAnyMap(
          "list_value",
          Any.pack(ListValue.newBuilder().addValues(numberValue).addValues(nullValue).build()));
      testAny.putAnyMap("number_value", Any.pack(numberValue));
      testAny.putAnyMap("any_value_number", Any.pack(Any.pack(numberValue)));
      testAny.putAnyMap("any_value_default", Any.pack(Any.getDefaultInstance()));
      testAny.putAnyMap("default", Any.getDefaultInstance());
  
      assertEquals(
          "{\n"
              + "  \"anyMap\": {\n"
              + "    \"int32_wrapper\": {\n"
              + "      \"@type\": \"type.googleapis.com/google.protobuf.Int32Value\",\n"
              + "      \"value\": 123\n"
              + "    },\n"
              + "    \"int64_wrapper\": {\n"
              + "      \"@type\": \"type.googleapis.com/google.protobuf.Int64Value\",\n"
              + "      \"value\": \"456\"\n"
              + "    },\n"
              + "    \"timestamp\": {\n"
              + "      \"@type\": \"type.googleapis.com/google.protobuf.Timestamp\",\n"
              + "      \"value\": \"1969-12-31T23:59:59Z\"\n"
              + "    },\n"
              + "    \"duration\": {\n"
              + "      \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n"
              + "      \"value\": \"12345.100s\"\n"
              + "    },\n"
              + "    \"field_mask\": {\n"
              + "      \"@type\": \"type.googleapis.com/google.protobuf.FieldMask\",\n"
              + "      \"value\": \"foo.bar,baz\"\n"
              + "    },\n"
              + "    \"struct\": {\n"
              + "      \"@type\": \"type.googleapis.com/google.protobuf.Struct\",\n"
              + "      \"value\": {\n"
              + "        \"number\": 1.125\n"
              + "      }\n"
              + "    },\n"
              + "    \"list_value\": {\n"
              + "      \"@type\": \"type.googleapis.com/google.protobuf.ListValue\",\n"
              + "      \"value\": [1.125, null]\n"
              + "    },\n"
              + "    \"number_value\": {\n"
              + "      \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n"
              + "      \"value\": 1.125\n"
              + "    },\n"
              + "    \"any_value_number\": {\n"
              + "      \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n"
              + "      \"value\": {\n"
              + "        \"@type\": \"type.googleapis.com/google.protobuf.Value\",\n"
              + "        \"value\": 1.125\n"
              + "      }\n"
              + "    },\n"
              + "    \"any_value_default\": {\n"
              + "      \"@type\": \"type.googleapis.com/google.protobuf.Any\",\n"
              + "      \"value\": {}\n"
              + "    },\n"
              + "    \"default\": {}\n"
              + "  }\n"
              + "}",
          printer.print(testAny.build()));
      assertRoundTripEquals(testAny.build(), registry);
    }
  
    public void testParserMissingTypeUrl() throws Exception {
      try {
        Any.Builder builder = Any.newBuilder();
        mergeFromJson("{\n" + "  \"optionalInt32\": 1234\n" + "}", builder);
        fail("Exception is expected.");
      } catch (IOException e) {
        // Expected.
      }
    }
  
    public void testParserUnexpectedTypeUrl() throws Exception {
      try {
        Any.Builder builder = Any.newBuilder();
        mergeFromJson(
            "{\n"
                + "  \"@type\": \"type.googleapis.com/json_test.TestAllTypes\",\n"
                + "  \"optionalInt32\": 12345\n"
                + "}",
            builder);
        fail("Exception is expected.");
      } catch (IOException e) {
        // Expected.
      }
    }
  
    public void testParserRejectTrailingComma() throws Exception {
      try {
        TestAllTypes.Builder builder = TestAllTypes.newBuilder();
        mergeFromJson("{\n" + "  \"optionalInt32\": 12345,\n" + "}", builder);
        fail("Exception is expected.");
      } catch (IOException e) {
        // Expected.
      }
  
      // TODO(xiaofeng): GSON allows trailing comma in arrays even after I set
      // the JsonReader to non-lenient mode. If we want to enforce strict JSON
      // compliance, we might want to switch to a different JSON parser or
      // implement one by ourselves.
      // try {
      //   TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      //   JsonFormat.merge(
      //       "{\n"
      //       + "  \"repeatedInt32\": [12345,]\n"
      //       + "}", builder);
      //   fail("Exception is expected.");
      // } catch (IOException e) {
      //   // Expected.
      // }
    }
  
    public void testParserRejectInvalidBase64() throws Exception {
      assertRejects("optionalBytes", "!@#$");
    }
  
    public void testParserAcceptBase64Variants() throws Exception {
      assertAccepts("optionalBytes", "AQI");  // No padding
      assertAccepts("optionalBytes", "-_w");  // base64Url, no padding
    }
  
    public void testParserRejectInvalidEnumValue() throws Exception {
      try {
        TestAllTypes.Builder builder = TestAllTypes.newBuilder();
        mergeFromJson("{\n" + "  \"optionalNestedEnum\": \"XXX\"\n" + "}", builder);
        fail("Exception is expected.");
      } catch (InvalidProtocolBufferException e) {
        // Expected.
      }
    }
  
    public void testParserUnknownFields() throws Exception {
      try {
        TestAllTypes.Builder builder = TestAllTypes.newBuilder();
        String json = "{\n" + "  \"unknownField\": \"XXX\"\n" + "}";
        JsonFormat.parser().merge(json, builder);
        fail("Exception is expected.");
      } catch (InvalidProtocolBufferException e) {
        // Expected.
      }
    }
  
    public void testParserIgnoringUnknownFields() throws Exception {
      TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      String json = "{\n" + "  \"unknownField\": \"XXX\"\n" + "}";
      JsonFormat.parser().ignoringUnknownFields().merge(json, builder);
    }
  
    public void testParserIntegerEnumValue() throws Exception {
      TestAllTypes.Builder actualBuilder = TestAllTypes.newBuilder();
      mergeFromJson("{\n" + "  \"optionalNestedEnum\": 2\n" + "}", actualBuilder);
  
      TestAllTypes expected = TestAllTypes.newBuilder().setOptionalNestedEnum(NestedEnum.BAZ).build();
      assertEquals(expected, actualBuilder.build());
    }
  
    public void testCustomJsonName() throws Exception {
      TestCustomJsonName message = TestCustomJsonName.newBuilder().setValue(12345).build();
      assertEquals("{\n" + "  \"@value\": 12345\n" + "}", JsonFormat.printer().print(message));
      assertRoundTripEquals(message);
    }
  
    public void testDefaultGsonDoesNotHtmlEscape() throws Exception {
      TestAllTypes message = TestAllTypes.newBuilder().setOptionalString("=").build();
      assertEquals(
          "{\n" + "  \"optionalString\": \"=\"" + "\n}", JsonFormat.printer().print(message));
    }
  
    public void testIncludingDefaultValueFields() throws Exception {
      TestAllTypes message = TestAllTypes.getDefaultInstance();
      assertEquals("{\n}", JsonFormat.printer().print(message));
      assertEquals(
          "{\n"
              + "  \"optionalInt32\": 0,\n"
              + "  \"optionalInt64\": \"0\",\n"
              + "  \"optionalUint32\": 0,\n"
              + "  \"optionalUint64\": \"0\",\n"
              + "  \"optionalSint32\": 0,\n"
              + "  \"optionalSint64\": \"0\",\n"
              + "  \"optionalFixed32\": 0,\n"
              + "  \"optionalFixed64\": \"0\",\n"
              + "  \"optionalSfixed32\": 0,\n"
              + "  \"optionalSfixed64\": \"0\",\n"
              + "  \"optionalFloat\": 0.0,\n"
              + "  \"optionalDouble\": 0.0,\n"
              + "  \"optionalBool\": false,\n"
              + "  \"optionalString\": \"\",\n"
              + "  \"optionalBytes\": \"\",\n"
              + "  \"optionalNestedEnum\": \"FOO\",\n"
              + "  \"repeatedInt32\": [],\n"
              + "  \"repeatedInt64\": [],\n"
              + "  \"repeatedUint32\": [],\n"
              + "  \"repeatedUint64\": [],\n"
              + "  \"repeatedSint32\": [],\n"
              + "  \"repeatedSint64\": [],\n"
              + "  \"repeatedFixed32\": [],\n"
              + "  \"repeatedFixed64\": [],\n"
              + "  \"repeatedSfixed32\": [],\n"
              + "  \"repeatedSfixed64\": [],\n"
              + "  \"repeatedFloat\": [],\n"
              + "  \"repeatedDouble\": [],\n"
              + "  \"repeatedBool\": [],\n"
              + "  \"repeatedString\": [],\n"
              + "  \"repeatedBytes\": [],\n"
              + "  \"repeatedNestedMessage\": [],\n"
              + "  \"repeatedNestedEnum\": []\n"
              + "}",
          JsonFormat.printer().includingDefaultValueFields().print(message));
  
      Set<FieldDescriptor> fixedFields = new HashSet<FieldDescriptor>();
      for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) {
        if (fieldDesc.getName().contains("_fixed")) {
          fixedFields.add(fieldDesc);
        }
      }
  
      assertEquals(
          "{\n"
              + "  \"optionalFixed32\": 0,\n"
              + "  \"optionalFixed64\": \"0\",\n"
              + "  \"repeatedFixed32\": [],\n"
              + "  \"repeatedFixed64\": []\n"
              + "}",
          JsonFormat.printer().includingDefaultValueFields(fixedFields).print(message));
  
      TestAllTypes messageNonDefaults =
          message.toBuilder().setOptionalInt64(1234).setOptionalFixed32(3232).build();
      assertEquals(
          "{\n"
              + "  \"optionalInt64\": \"1234\",\n"
              + "  \"optionalFixed32\": 3232,\n"
              + "  \"optionalFixed64\": \"0\",\n"
              + "  \"repeatedFixed32\": [],\n"
              + "  \"repeatedFixed64\": []\n"
              + "}",
          JsonFormat.printer().includingDefaultValueFields(fixedFields).print(messageNonDefaults));
  
      try {
        JsonFormat.printer().includingDefaultValueFields().includingDefaultValueFields();
        fail("IllegalStateException is expected.");
      } catch (IllegalStateException e) {
        // Expected.
        assertTrue(
            "Exception message should mention includingDefaultValueFields.",
            e.getMessage().contains("includingDefaultValueFields"));
      }
  
      try {
        JsonFormat.printer().includingDefaultValueFields().includingDefaultValueFields(fixedFields);
        fail("IllegalStateException is expected.");
      } catch (IllegalStateException e) {
        // Expected.
        assertTrue(
            "Exception message should mention includingDefaultValueFields.",
            e.getMessage().contains("includingDefaultValueFields"));
      }
  
      try {
        JsonFormat.printer().includingDefaultValueFields(fixedFields).includingDefaultValueFields();
        fail("IllegalStateException is expected.");
      } catch (IllegalStateException e) {
        // Expected.
        assertTrue(
            "Exception message should mention includingDefaultValueFields.",
            e.getMessage().contains("includingDefaultValueFields"));
      }
  
      try {
        JsonFormat.printer()
            .includingDefaultValueFields(fixedFields)
            .includingDefaultValueFields(fixedFields);
        fail("IllegalStateException is expected.");
      } catch (IllegalStateException e) {
        // Expected.
        assertTrue(
            "Exception message should mention includingDefaultValueFields.",
            e.getMessage().contains("includingDefaultValueFields"));
      }
  
      Set<FieldDescriptor> intFields = new HashSet<FieldDescriptor>();
      for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) {
        if (fieldDesc.getName().contains("_int")) {
          intFields.add(fieldDesc);
        }
      }
  
      try {
        JsonFormat.printer()
            .includingDefaultValueFields(intFields)
            .includingDefaultValueFields(fixedFields);
        fail("IllegalStateException is expected.");
      } catch (IllegalStateException e) {
        // Expected.
        assertTrue(
            "Exception message should mention includingDefaultValueFields.",
            e.getMessage().contains("includingDefaultValueFields"));
      }
  
      try {
        JsonFormat.printer().includingDefaultValueFields(null);
        fail("IllegalArgumentException is expected.");
      } catch (IllegalArgumentException e) {
        // Expected.
        assertTrue(
            "Exception message should mention includingDefaultValueFields.",
            e.getMessage().contains("includingDefaultValueFields"));
      }
  
      try {
        JsonFormat.printer().includingDefaultValueFields(Collections.<FieldDescriptor>emptySet());
        fail("IllegalArgumentException is expected.");
      } catch (IllegalArgumentException e) {
        // Expected.
        assertTrue(
            "Exception message should mention includingDefaultValueFields.",
            e.getMessage().contains("includingDefaultValueFields"));
      }
  
      TestMap mapMessage = TestMap.getDefaultInstance();
      assertEquals("{\n}", JsonFormat.printer().print(mapMessage));
      assertEquals(
          "{\n"
              + "  \"int32ToInt32Map\": {\n"
              + "  },\n"
              + "  \"int64ToInt32Map\": {\n"
              + "  },\n"
              + "  \"uint32ToInt32Map\": {\n"
              + "  },\n"
              + "  \"uint64ToInt32Map\": {\n"
              + "  },\n"
              + "  \"sint32ToInt32Map\": {\n"
              + "  },\n"
              + "  \"sint64ToInt32Map\": {\n"
              + "  },\n"
              + "  \"fixed32ToInt32Map\": {\n"
              + "  },\n"
              + "  \"fixed64ToInt32Map\": {\n"
              + "  },\n"
              + "  \"sfixed32ToInt32Map\": {\n"
              + "  },\n"
              + "  \"sfixed64ToInt32Map\": {\n"
              + "  },\n"
              + "  \"boolToInt32Map\": {\n"
              + "  },\n"
              + "  \"stringToInt32Map\": {\n"
              + "  },\n"
              + "  \"int32ToInt64Map\": {\n"
              + "  },\n"
              + "  \"int32ToUint32Map\": {\n"
              + "  },\n"
              + "  \"int32ToUint64Map\": {\n"
              + "  },\n"
              + "  \"int32ToSint32Map\": {\n"
              + "  },\n"
              + "  \"int32ToSint64Map\": {\n"
              + "  },\n"
              + "  \"int32ToFixed32Map\": {\n"
              + "  },\n"
              + "  \"int32ToFixed64Map\": {\n"
              + "  },\n"
              + "  \"int32ToSfixed32Map\": {\n"
              + "  },\n"
              + "  \"int32ToSfixed64Map\": {\n"
              + "  },\n"
              + "  \"int32ToFloatMap\": {\n"
              + "  },\n"
              + "  \"int32ToDoubleMap\": {\n"
              + "  },\n"
              + "  \"int32ToBoolMap\": {\n"
              + "  },\n"
              + "  \"int32ToStringMap\": {\n"
              + "  },\n"
              + "  \"int32ToBytesMap\": {\n"
              + "  },\n"
              + "  \"int32ToMessageMap\": {\n"
              + "  },\n"
              + "  \"int32ToEnumMap\": {\n"
              + "  }\n"
              + "}",
          JsonFormat.printer().includingDefaultValueFields().print(mapMessage));
  
      TestOneof oneofMessage = TestOneof.getDefaultInstance();
      assertEquals("{\n}", JsonFormat.printer().print(oneofMessage));
      assertEquals("{\n}", JsonFormat.printer().includingDefaultValueFields().print(oneofMessage));
  
      oneofMessage = TestOneof.newBuilder().setOneofInt32(42).build();
      assertEquals("{\n  \"oneofInt32\": 42\n}", JsonFormat.printer().print(oneofMessage));
      assertEquals(
          "{\n  \"oneofInt32\": 42\n}",
          JsonFormat.printer().includingDefaultValueFields().print(oneofMessage));
  
      TestOneof.Builder oneofBuilder = TestOneof.newBuilder();
      mergeFromJson("{\n" + "  \"oneofNullValue\": null \n" + "}", oneofBuilder);
      oneofMessage = oneofBuilder.build();
      assertEquals("{\n  \"oneofNullValue\": null\n}", JsonFormat.printer().print(oneofMessage));
      assertEquals(
          "{\n  \"oneofNullValue\": null\n}",
          JsonFormat.printer().includingDefaultValueFields().print(oneofMessage));
    }
  
    public void testPreservingProtoFieldNames() throws Exception {
      TestAllTypes message = TestAllTypes.newBuilder().setOptionalInt32(12345).build();
      assertEquals("{\n" + "  \"optionalInt32\": 12345\n" + "}", JsonFormat.printer().print(message));
      assertEquals(
          "{\n" + "  \"optional_int32\": 12345\n" + "}",
          JsonFormat.printer().preservingProtoFieldNames().print(message));
  
      // The json_name field option is ignored when configured to use original proto field names.
      TestCustomJsonName messageWithCustomJsonName =
          TestCustomJsonName.newBuilder().setValue(12345).build();
      assertEquals(
          "{\n" + "  \"value\": 12345\n" + "}",
          JsonFormat.printer().preservingProtoFieldNames().print(messageWithCustomJsonName));
  
      // Parsers accept both original proto field names and lowerCamelCase names.
      TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      JsonFormat.parser().merge("{\"optionalInt32\": 12345}", builder);
      assertEquals(12345, builder.getOptionalInt32());
      builder.clear();
      JsonFormat.parser().merge("{\"optional_int32\": 54321}", builder);
      assertEquals(54321, builder.getOptionalInt32());
    }
  
    public void testPrintingEnumsAsInts() throws Exception {
      TestAllTypes message = TestAllTypes.newBuilder().setOptionalNestedEnum(NestedEnum.BAR).build();
      assertEquals(
          "{\n" + "  \"optionalNestedEnum\": 1\n" + "}",
          JsonFormat.printer().printingEnumsAsInts().print(message));
    }
  
    public void testOmittingInsignificantWhiteSpace() throws Exception {
      TestAllTypes message = TestAllTypes.newBuilder().setOptionalInt32(12345).build();
      assertEquals(
          "{" + "\"optionalInt32\":12345" + "}",
          JsonFormat.printer().omittingInsignificantWhitespace().print(message));
      TestAllTypes message1 = TestAllTypes.getDefaultInstance();
      assertEquals("{}", JsonFormat.printer().omittingInsignificantWhitespace().print(message1));
      TestAllTypes.Builder builder = TestAllTypes.newBuilder();
      setAllFields(builder);
      TestAllTypes message2 = builder.build();
      assertEquals(
          "{"
              + "\"optionalInt32\":1234,"
              + "\"optionalInt64\":\"1234567890123456789\","
              + "\"optionalUint32\":5678,"
              + "\"optionalUint64\":\"2345678901234567890\","
              + "\"optionalSint32\":9012,"
              + "\"optionalSint64\":\"3456789012345678901\","
              + "\"optionalFixed32\":3456,"
              + "\"optionalFixed64\":\"4567890123456789012\","
              + "\"optionalSfixed32\":7890,"
              + "\"optionalSfixed64\":\"5678901234567890123\","
              + "\"optionalFloat\":1.5,"
              + "\"optionalDouble\":1.25,"
              + "\"optionalBool\":true,"
              + "\"optionalString\":\"Hello world!\","
              + "\"optionalBytes\":\"AAEC\","
              + "\"optionalNestedMessage\":{"
              + "\"value\":100"
              + "},"
              + "\"optionalNestedEnum\":\"BAR\","
              + "\"repeatedInt32\":[1234,234],"
              + "\"repeatedInt64\":[\"1234567890123456789\",\"234567890123456789\"],"
              + "\"repeatedUint32\":[5678,678],"
              + "\"repeatedUint64\":[\"2345678901234567890\",\"345678901234567890\"],"
              + "\"repeatedSint32\":[9012,10],"
              + "\"repeatedSint64\":[\"3456789012345678901\",\"456789012345678901\"],"
              + "\"repeatedFixed32\":[3456,456],"
              + "\"repeatedFixed64\":[\"4567890123456789012\",\"567890123456789012\"],"
              + "\"repeatedSfixed32\":[7890,890],"
              + "\"repeatedSfixed64\":[\"5678901234567890123\",\"678901234567890123\"],"
              + "\"repeatedFloat\":[1.5,11.5],"
              + "\"repeatedDouble\":[1.25,11.25],"
              + "\"repeatedBool\":[true,true],"
              + "\"repeatedString\":[\"Hello world!\",\"ello world!\"],"
              + "\"repeatedBytes\":[\"AAEC\",\"AQI=\"],"
              + "\"repeatedNestedMessage\":[{"
              + "\"value\":100"
              + "},{"
              + "\"value\":200"
              + "}],"
              + "\"repeatedNestedEnum\":[\"BAR\",\"BAZ\"]"
              + "}",
          toCompactJsonString(message2));
    }
  
    // Regression test for b/29892357
    public void testEmptyWrapperTypesInAny() throws Exception {
      JsonFormat.TypeRegistry registry =
          JsonFormat.TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build();
      JsonFormat.Parser parser = JsonFormat.parser().usingTypeRegistry(registry);
  
      Any.Builder builder = Any.newBuilder();
      parser.merge(
          "{\n"
              + "  \"@type\": \"type.googleapis.com/google.protobuf.BoolValue\",\n"
              + "  \"value\": false\n"
              + "}\n",
          builder);
      Any any = builder.build();
      assertEquals(0, any.getValue().size());
    }
  
    public void testRecursionLimit() throws Exception {
      String input =
          "{\n"
              + "  \"nested\": {\n"
              + "    \"nested\": {\n"
              + "      \"nested\": {\n"
              + "        \"nested\": {\n"
              + "          \"value\": 1234\n"
              + "        }\n"
              + "      }\n"
              + "    }\n"
              + "  }\n"
              + "}\n";
  
      JsonFormat.Parser parser = JsonFormat.parser();
      TestRecursive.Builder builder = TestRecursive.newBuilder();
      parser.merge(input, builder);
      TestRecursive message = builder.build();
      assertEquals(1234, message.getNested().getNested().getNested().getNested().getValue());
  
      parser = JsonFormat.parser().usingRecursionLimit(3);
      builder = TestRecursive.newBuilder();
      try {
        parser.merge(input, builder);
        fail("Exception is expected.");
      } catch (InvalidProtocolBufferException e) {
        // Expected.
      }
    }
  
    // Test that we are not leaking out JSON exceptions.
    public void testJsonException() throws Exception {
      InputStream throwingInputStream =
          new InputStream() {
            public int read() throws IOException {
              throw new IOException("12345");
            }
          };
      InputStreamReader throwingReader = new InputStreamReader(throwingInputStream);
      // When the underlying reader throws IOException, JsonFormat should forward
      // through this IOException.
      try {
        TestAllTypes.Builder builder = TestAllTypes.newBuilder();
        JsonFormat.parser().merge(throwingReader, builder);
        fail("Exception is expected.");
      } catch (IOException e) {
        assertEquals("12345", e.getMessage());
      }
  
      Reader invalidJsonReader = new StringReader("{ xxx - yyy }");
      // When the JSON parser throws parser exceptions, JsonFormat should turn
      // that into InvalidProtocolBufferException.
      try {
        TestAllTypes.Builder builder = TestAllTypes.newBuilder();
        JsonFormat.parser().merge(invalidJsonReader, builder);
        fail("Exception is expected.");
      } catch (InvalidProtocolBufferException e) {
        // Expected.
      }
    }
  }
  