| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| package org.jclouds.json; |
| |
| import static com.google.common.io.BaseEncoding.base16; |
| import static com.google.common.primitives.Bytes.asList; |
| import static org.testng.Assert.assertEquals; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.nio.charset.Charset; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| import org.jclouds.javax.annotation.Nullable; |
| import org.jclouds.json.config.GsonModule; |
| import org.jclouds.json.config.GsonModule.DefaultExclusionStrategy; |
| import org.testng.annotations.Test; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.gson.FieldAttributes; |
| import com.google.gson.Gson; |
| import com.google.gson.TypeAdapter; |
| import com.google.gson.TypeAdapterFactory; |
| import com.google.gson.reflect.TypeToken; |
| import com.google.gson.stream.JsonReader; |
| import com.google.gson.stream.JsonWriter; |
| import com.google.inject.AbstractModule; |
| import com.google.inject.Guice; |
| import com.google.inject.Provides; |
| import com.google.inject.TypeLiteral; |
| |
| @Test |
| public class JsonTest { |
| private Json json = Guice.createInjector(new GsonModule()).getInstance(Json.class); |
| |
| private static class ObjectNoDefaultConstructor { |
| private final String stringValue; |
| private final int intValue; |
| |
| public ObjectNoDefaultConstructor(String stringValue, int intValue) { |
| this.stringValue = stringValue; |
| this.intValue = intValue; |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + intValue; |
| result = prime * result + ((stringValue == null) ? 0 : stringValue.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (obj == null) |
| return false; |
| if (getClass() != obj.getClass()) |
| return false; |
| ObjectNoDefaultConstructor other = (ObjectNoDefaultConstructor) obj; |
| if (intValue != other.intValue) |
| return false; |
| if (stringValue == null) { |
| if (other.stringValue != null) |
| return false; |
| } else if (!stringValue.equals(other.stringValue)) |
| return false; |
| return true; |
| } |
| } |
| |
| public void testObjectNoDefaultConstructor() { |
| ObjectNoDefaultConstructor obj = new ObjectNoDefaultConstructor("foo", 1); |
| assertEquals(json.toJson(obj), "{\"stringValue\":\"foo\",\"intValue\":1}"); |
| ObjectNoDefaultConstructor obj2 = json.fromJson(json.toJson(obj), ObjectNoDefaultConstructor.class); |
| assertEquals(obj2, obj); |
| assertEquals(json.toJson(obj2), json.toJson(obj)); |
| } |
| |
| static class ExcludeStringValue implements DefaultExclusionStrategy { |
| public boolean shouldSkipClass(Class<?> clazz) { |
| return false; |
| } |
| |
| public boolean shouldSkipField(FieldAttributes f) { |
| return f.getName().equals("stringValue"); |
| } |
| } |
| |
| public void testExcluder() { |
| Json excluder = Guice.createInjector(new GsonModule(), new AbstractModule() { |
| protected void configure() { |
| bind(DefaultExclusionStrategy.class).to(ExcludeStringValue.class); |
| } |
| }).getInstance(Json.class); |
| ObjectNoDefaultConstructor obj = new ObjectNoDefaultConstructor("foo", 1); |
| assertEquals(excluder.toJson(obj), "{\"intValue\":1}"); |
| } |
| |
| private static class EnumInside { |
| private static enum Test { |
| FOO, BAR; |
| } |
| |
| private Test enumValue; |
| } |
| |
| private static class ByteList { |
| List<Byte> checksum; |
| } |
| |
| public void testByteList() { |
| ByteList bl = new ByteList(); |
| bl.checksum = asList(base16().lowerCase().decode("1dda05ed139664f1f89b9dec482b77c0")); |
| assertEquals(json.toJson(bl), "{\"checksum\":\"1dda05ed139664f1f89b9dec482b77c0\"}"); |
| assertEquals(json.fromJson(json.toJson(bl), ByteList.class).checksum, bl.checksum); |
| } |
| |
| public void testPropertiesSerializesDefaults() { |
| Properties props = new Properties(); |
| props.put("string", "string"); |
| props.put("number", "1"); |
| props.put("boolean", "true"); |
| assertEquals(json.toJson(props), "{\"string\":\"string\",\"boolean\":\"true\",\"number\":\"1\"}"); |
| Properties props3 = new Properties(props); |
| assertEquals(json.toJson(props3), "{\"string\":\"string\",\"boolean\":\"true\",\"number\":\"1\"}"); |
| Properties props2 = json.fromJson(json.toJson(props), Properties.class); |
| assertEquals(props2, props); |
| assertEquals(json.toJson(props2), json.toJson(props)); |
| } |
| |
| public void testMapStringObjectWithAllValidValuesOneDeep() { |
| Map<String, Object> map = ImmutableMap.<String, Object>builder() |
| .put("string", "string") |
| .put("map", ImmutableMap.of("key", "value")) |
| .put("list", ImmutableList.of("key", "value")) |
| .put("boolean", true) |
| .put("number", 1.0) |
| .build(); |
| assertEquals(json.toJson(map), |
| "{\"string\":\"string\",\"map\":{\"key\":\"value\"},\"list\":[\"key\",\"value\"],\"boolean\":true,\"number\":1.0}"); |
| Map<String, Object> map2 = json.fromJson(json.toJson(map), new TypeLiteral<Map<String, Object>>() { |
| }.getType()); |
| assertEquals(map2, map); |
| assertEquals(json.toJson(map2), json.toJson(map)); |
| } |
| |
| public void testMapStringObjectWithNumericalKeysConvertToStrings() { |
| Map<String, Object> map = ImmutableMap.<String, Object> of("map", ImmutableMap.of(1, "value")); |
| assertEquals(json.toJson(map), "{\"map\":{\"1\":\"value\"}}"); |
| Map<String, Object> map2 = json.fromJson(json.toJson(map), new TypeLiteral<Map<String, Object>>() { |
| }.getType()); |
| // note conversion.. ensures valid |
| assertEquals(map2, ImmutableMap.<String, Object> of("map", ImmutableMap.of("1", "value"))); |
| assertEquals(json.toJson(map2), json.toJson(map)); |
| } |
| |
| public void testMapStringObjectWithBooleanKeysConvertToStrings() { |
| Map<String, Object> map = ImmutableMap.<String, Object> of("map", ImmutableMap.of(true, "value")); |
| assertEquals(json.toJson(map), "{\"map\":{\"true\":\"value\"}}"); |
| Map<String, Object> map2 = json.fromJson(json.toJson(map), new TypeLiteral<Map<String, Object>>() { |
| }.getType()); |
| // note conversion.. ensures valid |
| assertEquals(map2, ImmutableMap.<String, Object> of("map", ImmutableMap.of("true", "value"))); |
| assertEquals(json.toJson(map2), json.toJson(map)); |
| } |
| |
| public void testDeserializeEnum() { |
| assertEquals(json.fromJson("{enumValue : \"FOO\"}", EnumInside.class).enumValue, EnumInside.Test.FOO); |
| } |
| |
| @Test(expectedExceptions = IllegalArgumentException.class) |
| public void testDeserializeEnumWhenBadValue() { |
| assertEquals(json.fromJson("{enumValue : \"s\"}", EnumInside.class).enumValue, EnumInside.Test.FOO); |
| } |
| |
| private static class EnumInsideWithParser { |
| private static enum Test { |
| FOO, BAR, UNRECOGNIZED; |
| |
| @SuppressWarnings("unused") |
| public static Test fromValue(String state) { |
| try { |
| return valueOf(state); |
| } catch (IllegalArgumentException e) { |
| return UNRECOGNIZED; |
| } |
| } |
| } |
| |
| private Test enumValue; |
| } |
| |
| public void testDeserializeEnumWithParser() { |
| assertEquals(json.fromJson("{enumValue : \"FOO\"}", EnumInsideWithParser.class).enumValue, |
| EnumInsideWithParser.Test.FOO); |
| } |
| |
| public void testDeserializeEnumWithParserAndBadValue() { |
| assertEquals(json.fromJson("{enumValue : \"sd\"}", EnumInsideWithParser.class).enumValue, |
| EnumInsideWithParser.Test.UNRECOGNIZED); |
| } |
| |
| @AutoValue |
| abstract static class SerializedNamesType { |
| abstract String id(); |
| |
| @Nullable abstract Map<String, String> volumes(); |
| |
| @SerializedNames({ "Id", "Volumes" }) |
| private static SerializedNamesType create(String id, Map<String, String> volumes) { |
| return new AutoValue_JsonTest_SerializedNamesType(id, volumes); |
| } |
| } |
| |
| public void autoValueSerializedNames() { |
| Json json = Guice.createInjector(new GsonModule()).getInstance(Json.class); |
| |
| SerializedNamesType resource = SerializedNamesType.create("1234", Collections.<String, String>emptyMap()); |
| String spinalJson = "{\"Id\":\"1234\",\"Volumes\":{}}"; |
| |
| assertEquals(json.toJson(resource), spinalJson); |
| assertEquals(json.fromJson(spinalJson, SerializedNamesType.class), resource); |
| } |
| |
| public void autoValueWithBuilder() { |
| Json json = Guice.createInjector(new GsonModule()).getInstance(Json.class); |
| |
| SerializedNamesWithBuilder resource = SerializedNamesWithBuilder.builder().id("1234").number(2).build(); |
| String spinalJson = "{\"custom_id_name\":\"1234\",\"number\":2}"; |
| |
| assertEquals(json.toJson(resource), spinalJson); |
| assertEquals(json.fromJson(spinalJson, SerializedNamesWithBuilder.class), resource); |
| } |
| |
| public void autoValueWithBuilderNested() { |
| Json json = Guice.createInjector(new GsonModule()).getInstance(Json.class); |
| |
| SerializedNamesNestedWithBuilder resource = SerializedNamesNestedWithBuilder.builder().id("1234") |
| .snwb(ImmutableList.of(SerializedNamesWithBuilder.builder().id("5678").number(1).build())).build(); |
| |
| String spinalJson = "{\"nested_id_name\":\"1234\",\"serialized_name_with_builder\":[{\"custom_id_name\":\"5678\",\"number\":1}]}"; |
| |
| assertEquals(json.toJson(resource), spinalJson); |
| assertEquals(json.fromJson(spinalJson, SerializedNamesNestedWithBuilder.class), resource); |
| } |
| |
| public void autoValueWithBuilderMissingNested() { |
| Json json = Guice.createInjector(new GsonModule()).getInstance(Json.class); |
| |
| SerializedNamesNestedWithBuilder resource = SerializedNamesNestedWithBuilder.builder().id("1234") |
| .snwb(ImmutableList.<SerializedNamesWithBuilder>of()).build(); |
| |
| String spinalJson = "{\"nested_id_name\":\"1234\",\"serialized_name_with_builder\":[]}"; |
| |
| assertEquals(json.toJson(resource), spinalJson); |
| assertEquals(json.fromJson(spinalJson, SerializedNamesNestedWithBuilder.class), resource); |
| } |
| |
| @AutoValue |
| abstract static class SerializedNamesNestedWithBuilder { |
| public abstract String getId(); |
| @Nullable |
| public abstract List<SerializedNamesWithBuilder> getSnwb(); |
| |
| public static Builder builder() { |
| return new AutoValue_JsonTest_SerializedNamesNestedWithBuilder.Builder(); |
| } |
| public abstract Builder toBuilder(); |
| |
| @AutoValue.Builder |
| public interface Builder { |
| Builder id(String id); |
| Builder snwb(List<SerializedNamesWithBuilder> snwb); |
| SerializedNamesNestedWithBuilder build(); |
| } |
| |
| @SerializedNames({"nested_id_name", "serialized_name_with_builder"}) |
| private static SerializedNamesNestedWithBuilder create(String id, List<SerializedNamesWithBuilder> snwb) { |
| return builder().id(id).snwb(snwb).build(); |
| } |
| } |
| |
| @AutoValue |
| abstract static class SerializedNamesWithBuilder { |
| public abstract String getId(); |
| public abstract int getNumber(); |
| |
| public static Builder builder() { |
| return new AutoValue_JsonTest_SerializedNamesWithBuilder.Builder(); |
| } |
| public abstract Builder toBuilder(); |
| |
| @AutoValue.Builder |
| public interface Builder { |
| Builder id(String id); |
| Builder number(int number); |
| SerializedNamesWithBuilder build(); |
| } |
| |
| @SerializedNames({"custom_id_name", "number"}) |
| private static SerializedNamesWithBuilder create(String id, int number) { |
| return builder().id(id).number(number).build(); |
| } |
| } |
| |
| @AutoValue |
| abstract static class SerializedNamesTooFewType { |
| abstract String id(); |
| |
| @Nullable abstract Map<String, String> volumes(); |
| |
| @SerializedNames("Id") // TODO: check things like this with error-prone, not runtime! |
| private static SerializedNamesTooFewType create(String id, Map<String, String> volumes) { |
| return new AutoValue_JsonTest_SerializedNamesTooFewType(id, volumes); |
| } |
| } |
| |
| /** Common problem. Someone adds a parameter, but forgets to add it to the names list. */ |
| @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "Incorrect number .*") |
| public void autoValueSerializedNames_tooFew() { |
| Json json = Guice.createInjector(new GsonModule()).getInstance(Json.class); |
| json.toJson(SerializedNamesTooFewType.create("1234", null)); |
| } |
| |
| public void autoValueSerializedNames_nullValueInJson() { |
| Json json = Guice.createInjector(new GsonModule()).getInstance(Json.class); |
| |
| assertEquals(json.fromJson("{\"Id\":\"1234\",\"Volumes\":null}", SerializedNamesType.class), |
| SerializedNamesType.create("1234", null)); |
| } |
| |
| public void nonUtf8InputStream() { |
| Json json = Guice.createInjector(new GsonModule()).getInstance(Json.class); |
| String jsonValue = "{\"stringValue\":\"1234\",\"intValue\":1234}"; |
| Charset ebcdicCharset = Charset.forName("IBM-1047"); |
| ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonValue.getBytes(ebcdicCharset)); |
| assertEquals(json.fromJson(inputStream, ebcdicCharset, ObjectNoDefaultConstructor.class), |
| new ObjectNoDefaultConstructor("1234", 1234)); |
| } |
| |
| @AutoValue |
| abstract static class NestedSerializedNamesType { |
| abstract SerializedNamesType item(); |
| abstract List<SerializedNamesType> items(); |
| |
| @SerializedNames({ "Item", "Items" }) |
| private static NestedSerializedNamesType create(SerializedNamesType item, List<SerializedNamesType> items) { |
| return new AutoValue_JsonTest_NestedSerializedNamesType(item, items); |
| } |
| } |
| |
| private final NestedSerializedNamesType nested = NestedSerializedNamesType |
| .create(SerializedNamesType.create("1234", Collections.<String, String>emptyMap()), |
| Arrays.asList(SerializedNamesType.create("5678", ImmutableMap.of("Foo", "Bar")))); |
| |
| public void autoValueSerializedNames_nestedType() { |
| Json json = Guice.createInjector(new GsonModule()).getInstance(Json.class); |
| |
| String spinalJson = "{\"Item\":{\"Id\":\"1234\",\"Volumes\":{}},\"Items\":[{\"Id\":\"5678\",\"Volumes\":{\"Foo\":\"Bar\"}}]}"; |
| |
| assertEquals(json.toJson(nested), spinalJson); |
| assertEquals(json.fromJson(spinalJson, NestedSerializedNamesType.class), nested); |
| } |
| |
| public void autoValueSerializedNames_overriddenTypeAdapterFactory() { |
| Json json = Guice.createInjector(new GsonModule(), new AbstractModule() { |
| @Override protected void configure() { |
| } |
| |
| @Provides public Set<TypeAdapterFactory> typeAdapterFactories() { |
| return ImmutableSet.<TypeAdapterFactory>of(new NestedSerializedNamesTypeAdapterFactory()); |
| } |
| }).getInstance(Json.class); |
| |
| assertEquals(json.toJson(nested), "{\"id\":\"1234\",\"count\":1}"); |
| assertEquals(json.fromJson("{\"id\":\"1234\",\"count\":1}", NestedSerializedNamesType.class), nested); |
| } |
| |
| private class NestedSerializedNamesTypeAdapterFactory extends TypeAdapter<NestedSerializedNamesType> |
| implements TypeAdapterFactory { |
| |
| @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { |
| if (!(NestedSerializedNamesType.class.isAssignableFrom(typeToken.getRawType()))) { |
| return null; |
| } |
| return (TypeAdapter<T>) this; |
| } |
| |
| @Override public void write(JsonWriter out, NestedSerializedNamesType value) throws IOException { |
| out.beginObject(); |
| out.name("id").value(value.item().id()); |
| out.name("count").value(value.items().size()); |
| out.endObject(); |
| } |
| |
| @Override public NestedSerializedNamesType read(JsonReader in) throws IOException { |
| in.beginObject(); |
| in.nextName(); |
| in.nextString(); |
| in.nextName(); |
| in.nextInt(); |
| in.endObject(); |
| return nested; |
| } |
| } |
| } |