├── .buildscript ├── deploy_snapshot.sh └── settings.xml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── adapters ├── README.md ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── squareup │ │ └── moshi │ │ ├── Rfc3339DateJsonAdapter.java │ │ └── adapters │ │ ├── EnumJsonAdapter.java │ │ ├── Iso8601Utils.java │ │ ├── PolymorphicJsonAdapterFactory.java │ │ └── Rfc3339DateJsonAdapter.java │ └── test │ └── java │ └── com │ └── squareup │ └── moshi │ └── adapters │ ├── EnumJsonAdapterTest.java │ ├── PolymorphicJsonAdapterFactoryTest.java │ └── Rfc3339DateJsonAdapterTest.java ├── checkstyle.xml ├── deploy_javadoc.sh ├── examples ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── squareup │ └── moshi │ └── recipes │ ├── ByteStrings.java │ ├── CardAdapter.java │ ├── CustomAdapterFactory.java │ ├── CustomAdapterWithDelegate.java │ ├── CustomFieldName.java │ ├── CustomQualifier.java │ ├── CustomTypeAdapter.java │ ├── DefaultOnDataMismatchAdapter.java │ ├── FallbackEnum.java │ ├── FromJsonWithoutStrings.java │ ├── MultipleFormats.java │ ├── ReadAndWriteRfc3339Dates.java │ ├── ReadJson.java │ ├── ReadJsonList.java │ ├── RecoverFromTypeMismatch.java │ ├── Unwrap.java │ ├── WriteJson.java │ ├── models │ ├── BlackjackHand.java │ ├── Card.java │ ├── Player.java │ ├── Suit.java │ └── Tournament.java │ └── package-info.java ├── kotlin ├── codegen │ ├── pom.xml │ └── src │ │ ├── assembly │ │ └── dokka.xml │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── squareup │ │ │ └── moshi │ │ │ └── kotlin │ │ │ └── codegen │ │ │ ├── AppliedType.kt │ │ │ ├── JsonClassCodegenProcessor.kt │ │ │ ├── MoshiCachedClassInspector.kt │ │ │ ├── api │ │ │ ├── AdapterGenerator.kt │ │ │ ├── DelegateKey.kt │ │ │ ├── PropertyGenerator.kt │ │ │ ├── TargetConstructor.kt │ │ │ ├── TargetParameter.kt │ │ │ ├── TargetProperty.kt │ │ │ ├── TargetType.kt │ │ │ ├── TypeRenderer.kt │ │ │ └── kotlintypes.kt │ │ │ └── metadata.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── squareup │ │ └── moshi │ │ └── kotlin │ │ └── codegen │ │ ├── JavaSuperclass.java │ │ └── JsonClassCodegenProcessorTest.kt ├── reflect │ ├── pom.xml │ └── src │ │ ├── assembly │ │ └── dokka.xml │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── squareup │ │ │ └── moshi │ │ │ ├── KotlinJsonAdapter.kt │ │ │ └── kotlin │ │ │ └── reflect │ │ │ └── KotlinJsonAdapter.kt │ │ ├── resources │ │ └── META-INF │ │ │ └── proguard │ │ │ └── moshi-kotlin.pro │ │ └── test │ │ └── java │ │ └── com │ │ └── squareup │ │ └── moshi │ │ └── kotlin │ │ └── reflect │ │ └── KotlinJsonAdapterTest.kt └── tests │ ├── pom.xml │ └── src │ ├── assembly │ └── dokka.xml │ └── test │ └── kotlin │ └── com │ └── squareup │ └── moshi │ └── kotlin │ ├── DefaultConstructorTest.kt │ ├── DualKotlinTest.kt │ ├── codegen │ ├── GeneratedAdaptersTest.kt │ ├── GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter.kt │ ├── LooksLikeAClass │ │ └── ClassInPackageThatLooksLikeAClass.kt │ └── MultipleMasksTest.kt │ └── reflect │ ├── -MoshiKotlinExtensions.kt │ └── KotlinJsonAdapterTest.kt ├── moshi ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── squareup │ │ │ └── moshi │ │ │ ├── AdapterMethodsFactory.java │ │ │ ├── ArrayJsonAdapter.java │ │ │ ├── ClassFactory.java │ │ │ ├── ClassJsonAdapter.java │ │ │ ├── CollectionJsonAdapter.java │ │ │ ├── FromJson.java │ │ │ ├── Json.java │ │ │ ├── JsonAdapter.java │ │ │ ├── JsonClass.java │ │ │ ├── JsonDataException.java │ │ │ ├── JsonEncodingException.java │ │ │ ├── JsonQualifier.java │ │ │ ├── JsonReader.java │ │ │ ├── JsonScope.java │ │ │ ├── JsonUtf8Reader.java │ │ │ ├── JsonUtf8Writer.java │ │ │ ├── JsonValueReader.java │ │ │ ├── JsonValueWriter.java │ │ │ ├── JsonWriter.java │ │ │ ├── LinkedHashTreeMap.java │ │ │ ├── MapJsonAdapter.java │ │ │ ├── Moshi.java │ │ │ ├── StandardJsonAdapters.java │ │ │ ├── ToJson.java │ │ │ ├── Types.java │ │ │ ├── internal │ │ │ ├── NonNullJsonAdapter.java │ │ │ ├── NullSafeJsonAdapter.java │ │ │ └── Util.java │ │ │ └── package-info.java │ └── resources │ │ └── META-INF │ │ └── proguard │ │ └── moshi.pro │ └── test │ └── java │ ├── android │ └── util │ │ └── Pair.java │ └── com │ └── squareup │ └── moshi │ ├── AdapterMethodsTest.java │ ├── CircularAdaptersTest.java │ ├── ClassJsonAdapterTest.java │ ├── DeferredAdapterTest.java │ ├── FlattenTest.java │ ├── JsonAdapterTest.java │ ├── JsonCodecFactory.java │ ├── JsonQualifiersTest.java │ ├── JsonReaderPathTest.java │ ├── JsonReaderTest.java │ ├── JsonUtf8ReaderTest.java │ ├── JsonUtf8WriterTest.java │ ├── JsonValueReaderTest.java │ ├── JsonValueWriterTest.java │ ├── JsonWriterPathTest.java │ ├── JsonWriterTest.java │ ├── LinkedHashTreeMapTest.java │ ├── MapJsonAdapterTest.java │ ├── MoshiTest.java │ ├── ObjectAdapterTest.java │ ├── PromoteNameToValueTest.java │ ├── RecursiveTypesResolveTest.java │ ├── TestUtil.java │ └── TypesTest.java └── pom.xml /.buildscript/deploy_snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo. 4 | # 5 | # Adapted from https://coderwall.com/p/9b_lfq and 6 | # https://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ 7 | 8 | SLUG="square/moshi" 9 | JDK="openjdk8" 10 | BRANCH="master" 11 | 12 | set -e 13 | 14 | if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then 15 | echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'." 16 | elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then 17 | echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'." 18 | elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 19 | echo "Skipping snapshot deployment: was pull request." 20 | elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then 21 | echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'." 22 | else 23 | echo "Deploying snapshot..." 24 | mvn clean source:jar javadoc:jar deploy --settings=".buildscript/settings.xml" -Dmaven.test.skip=true 25 | echo "Snapshot deployed!" 26 | fi 27 | -------------------------------------------------------------------------------- /.buildscript/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sonatype-nexus-snapshots 5 | ${env.CI_DEPLOY_USERNAME} 6 | ${env.CI_DEPLOY_PASSWORD} 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | eclipsebin 5 | 6 | bin 7 | gen 8 | build 9 | out 10 | lib 11 | 12 | target 13 | pom.xml.* 14 | release.properties 15 | dependency-reduced-pom.xml 16 | 17 | .idea 18 | *.iml 19 | *.ipr 20 | *.iws 21 | classes 22 | 23 | obj 24 | 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - openjdk8 5 | 6 | after_success: 7 | - .buildscript/deploy_snapshot.sh 8 | 9 | env: 10 | global: 11 | - secure: "TM2N/yo3azbA7+J6WG0W1RmK4vvS7b4JzvJP8NcXpoZCQw2Je1nQqmKS3F/04/DEUf8+SKVh2thKEeUxLN8knCO0OQLxeKIFj9PLvtgHnV8beB+pPxUdpEvMlB6ISeDsOZ098zoax28zfMwR7+uMeGe2VbtTSpwtqt4jkWx3ooA=" 12 | - secure: "A5flBI/PCmmxuHjqG73CIXqHYg8X1GWloA3jv0zrhCAuK5m3nAvt7JY2qom22ttEY9JI32dlArxhusrKib18gKNgZbxzoN5PydkG6T0uuGZw+uZM6jPiTlvkC0F4ikL6lg1dBap4BoakDMwAPfb11UpETamBGKOWdopLqw6T6w0=" 13 | 14 | branches: 15 | except: 16 | - gh-pages 17 | 18 | notifications: 19 | email: false 20 | 21 | cache: 22 | directories: 23 | - $HOME/.m2 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | If you would like to contribute code to Moshi you can do so through GitHub by 5 | forking the repository and sending a pull request. 6 | 7 | When submitting code, please make every effort to follow existing conventions 8 | and style in order to keep the code as readable as possible. Please also make 9 | sure your code compiles by running `mvn clean verify`. Checkstyle failures 10 | during compilation indicate errors in your style and can be viewed in the 11 | `checkstyle-result.xml` file. 12 | 13 | Before your code can be accepted into the project you must also sign the 14 | [Individual Contributor License Agreement (CLA)][1]. 15 | 16 | 17 | [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1 18 | -------------------------------------------------------------------------------- /adapters/README.md: -------------------------------------------------------------------------------- 1 | Adapters 2 | =================== 3 | 4 | Prebuilt Moshi `JsonAdapter`s for various things, such as `Rfc3339DateJsonAdapter` for parsing `java.util.Date`s 5 | 6 | To use, supply an instance of your desired converter when building your `Moshi` instance. 7 | 8 | ```java 9 | Moshi moshi = new Moshi.Builder() 10 | .add(Date.class, new Rfc3339DateJsonAdapter()) 11 | //etc 12 | .build(); 13 | ``` 14 | 15 | Download 16 | -------- 17 | 18 | Download [the latest JAR][1] or grab via [Maven][2]: 19 | ```xml 20 | 21 | com.squareup.moshi 22 | moshi-adapters 23 | latest.version 24 | 25 | ``` 26 | or [Gradle][2]: 27 | ```groovy 28 | implementation 'com.squareup.moshi:moshi-adapters:latest.version' 29 | ``` 30 | 31 | Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. 32 | 33 | 34 | 35 | [1]: https://search.maven.org/remote_content?g=com.squareup.moshi&a=moshi-adapters&v=LATEST 36 | [2]: http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.squareup.moshi%22%20a%3A%22moshi-adapters%22 37 | [snap]: https://oss.sonatype.org/content/repositories/snapshots/com/squareup/moshi/moshi-adapters/ 38 | -------------------------------------------------------------------------------- /adapters/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.squareup.moshi 8 | moshi-parent 9 | 1.10.0-SNAPSHOT 10 | 11 | 12 | moshi-adapters 13 | 14 | 15 | 16 | com.squareup.moshi 17 | moshi 18 | ${project.version} 19 | 20 | 21 | com.google.code.findbugs 22 | jsr305 23 | provided 24 | 25 | 26 | junit 27 | junit 28 | test 29 | 30 | 31 | org.assertj 32 | assertj-core 33 | test 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-jar-plugin 42 | 43 | 44 | 45 | com.squareup.moshi.adapters 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /adapters/src/main/java/com/squareup/moshi/Rfc3339DateJsonAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.io.IOException; 19 | import java.util.Date; 20 | 21 | /** 22 | * @deprecated this class moved to avoid a package name conflict in the Java Platform Module System. 23 | * The new class is {@code com.squareup.moshi.adapters.Rfc3339DateJsonAdapter}. 24 | */ 25 | public final class Rfc3339DateJsonAdapter extends JsonAdapter { 26 | private final com.squareup.moshi.adapters.Rfc3339DateJsonAdapter delegate 27 | = new com.squareup.moshi.adapters.Rfc3339DateJsonAdapter(); 28 | 29 | @Override public Date fromJson(JsonReader reader) throws IOException { 30 | return delegate.fromJson(reader); 31 | } 32 | 33 | @Override public void toJson(JsonWriter writer, Date value) throws IOException { 34 | delegate.toJson(writer, value); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /adapters/src/main/java/com/squareup/moshi/adapters/EnumJsonAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.adapters; 17 | 18 | import com.squareup.moshi.Json; 19 | import com.squareup.moshi.JsonAdapter; 20 | import com.squareup.moshi.JsonDataException; 21 | import com.squareup.moshi.JsonReader; 22 | import com.squareup.moshi.JsonWriter; 23 | import java.io.IOException; 24 | import java.util.Arrays; 25 | import javax.annotation.Nullable; 26 | 27 | /** 28 | * A JsonAdapter for enums that allows having a fallback enum value when a deserialized string does 29 | * not match any enum value. To use, add this as an adapter for your enum type on your {@link 30 | * com.squareup.moshi.Moshi.Builder Moshi.Builder}: 31 | * 32 | *
 {@code
 33 |  *
 34 |  *   Moshi moshi = new Moshi.Builder()
 35 |  *       .add(CurrencyCode.class, EnumJsonAdapter.create(CurrencyCode.class)
 36 |  *           .withUnknownFallback(CurrencyCode.USD))
 37 |  *       .build();
 38 |  * }
39 | */ 40 | public final class EnumJsonAdapter> extends JsonAdapter { 41 | final Class enumType; 42 | final String[] nameStrings; 43 | final T[] constants; 44 | final JsonReader.Options options; 45 | final boolean useFallbackValue; 46 | final @Nullable T fallbackValue; 47 | 48 | public static > EnumJsonAdapter create(Class enumType) { 49 | return new EnumJsonAdapter<>(enumType, null, false); 50 | } 51 | 52 | /** 53 | * Create a new adapter for this enum with a fallback value to use when the JSON string does not 54 | * match any of the enum's constants. Note that this value will not be used when the JSON value is 55 | * null, absent, or not a string. Also, the string values are case-sensitive, and this fallback 56 | * value will be used even on case mismatches. 57 | */ 58 | public EnumJsonAdapter withUnknownFallback(@Nullable T fallbackValue) { 59 | return new EnumJsonAdapter<>(enumType, fallbackValue, true); 60 | } 61 | 62 | EnumJsonAdapter(Class enumType, @Nullable T fallbackValue, boolean useFallbackValue) { 63 | this.enumType = enumType; 64 | this.fallbackValue = fallbackValue; 65 | this.useFallbackValue = useFallbackValue; 66 | try { 67 | constants = enumType.getEnumConstants(); 68 | nameStrings = new String[constants.length]; 69 | for (int i = 0; i < constants.length; i++) { 70 | String constantName = constants[i].name(); 71 | Json annotation = enumType.getField(constantName).getAnnotation(Json.class); 72 | String name = annotation != null ? annotation.name() : constantName; 73 | nameStrings[i] = name; 74 | } 75 | options = JsonReader.Options.of(nameStrings); 76 | } catch (NoSuchFieldException e) { 77 | throw new AssertionError("Missing field in " + enumType.getName(), e); 78 | } 79 | } 80 | 81 | @Override public @Nullable T fromJson(JsonReader reader) throws IOException { 82 | int index = reader.selectString(options); 83 | if (index != -1) return constants[index]; 84 | 85 | String path = reader.getPath(); 86 | if (!useFallbackValue) { 87 | String name = reader.nextString(); 88 | throw new JsonDataException("Expected one of " 89 | + Arrays.asList(nameStrings) + " but was " + name + " at path " + path); 90 | } 91 | if (reader.peek() != JsonReader.Token.STRING) { 92 | throw new JsonDataException( 93 | "Expected a string but was " + reader.peek() + " at path " + path); 94 | } 95 | reader.skipValue(); 96 | return fallbackValue; 97 | } 98 | 99 | @Override public void toJson(JsonWriter writer, T value) throws IOException { 100 | if (value == null) { 101 | throw new NullPointerException( 102 | "value was null! Wrap in .nullSafe() to write nullable values."); 103 | } 104 | writer.value(nameStrings[value.ordinal()]); 105 | } 106 | 107 | @Override public String toString() { 108 | return "EnumJsonAdapter(" + enumType.getName() + ")"; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /adapters/src/main/java/com/squareup/moshi/adapters/Rfc3339DateJsonAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.adapters; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.JsonReader; 20 | import com.squareup.moshi.JsonWriter; 21 | import java.io.IOException; 22 | import java.util.Date; 23 | 24 | /** 25 | * Formats dates using RFC 3339, which is 26 | * formatted like {@code 2015-09-26T18:23:50.250Z}. This adapter is null-safe. To use, add this as 27 | * an adapter for {@code Date.class} on your {@link com.squareup.moshi.Moshi.Builder Moshi.Builder}: 28 | * 29 | *
 {@code
30 |  *
31 |  *   Moshi moshi = new Moshi.Builder()
32 |  *       .add(Date.class, new Rfc3339DateJsonAdapter())
33 |  *       .build();
34 |  * }
35 | */ 36 | public final class Rfc3339DateJsonAdapter extends JsonAdapter { 37 | @Override public synchronized Date fromJson(JsonReader reader) throws IOException { 38 | if (reader.peek() == JsonReader.Token.NULL) { 39 | return reader.nextNull(); 40 | } 41 | String string = reader.nextString(); 42 | return Iso8601Utils.parse(string); 43 | } 44 | 45 | @Override public synchronized void toJson(JsonWriter writer, Date value) throws IOException { 46 | if (value == null) { 47 | writer.nullValue(); 48 | } else { 49 | String string = Iso8601Utils.format(value); 50 | writer.value(string); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /adapters/src/test/java/com/squareup/moshi/adapters/EnumJsonAdapterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.adapters; 17 | 18 | import com.squareup.moshi.Json; 19 | import com.squareup.moshi.JsonDataException; 20 | import com.squareup.moshi.JsonReader; 21 | import okio.Buffer; 22 | import org.junit.Test; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | import static org.junit.Assert.fail; 26 | 27 | @SuppressWarnings("CheckReturnValue") 28 | public final class EnumJsonAdapterTest { 29 | @Test public void toAndFromJson() throws Exception { 30 | EnumJsonAdapter adapter = EnumJsonAdapter.create(Roshambo.class); 31 | assertThat(adapter.fromJson("\"ROCK\"")).isEqualTo(Roshambo.ROCK); 32 | assertThat(adapter.toJson(Roshambo.PAPER)).isEqualTo("\"PAPER\""); 33 | } 34 | 35 | @Test public void withJsonName() throws Exception { 36 | EnumJsonAdapter adapter = EnumJsonAdapter.create(Roshambo.class); 37 | assertThat(adapter.fromJson("\"scr\"")).isEqualTo(Roshambo.SCISSORS); 38 | assertThat(adapter.toJson(Roshambo.SCISSORS)).isEqualTo("\"scr\""); 39 | } 40 | 41 | @Test public void withoutFallbackValue() throws Exception { 42 | EnumJsonAdapter adapter = EnumJsonAdapter.create(Roshambo.class); 43 | JsonReader reader = JsonReader.of(new Buffer().writeUtf8("\"SPOCK\"")); 44 | try { 45 | adapter.fromJson(reader); 46 | fail(); 47 | } catch (JsonDataException expected) { 48 | assertThat(expected).hasMessage( 49 | "Expected one of [ROCK, PAPER, scr] but was SPOCK at path $"); 50 | } 51 | assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); 52 | } 53 | 54 | @Test public void withFallbackValue() throws Exception { 55 | EnumJsonAdapter adapter = EnumJsonAdapter.create(Roshambo.class) 56 | .withUnknownFallback(Roshambo.ROCK); 57 | JsonReader reader = JsonReader.of(new Buffer().writeUtf8("\"SPOCK\"")); 58 | assertThat(adapter.fromJson(reader)).isEqualTo(Roshambo.ROCK); 59 | assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); 60 | } 61 | 62 | @Test public void withNullFallbackValue() throws Exception { 63 | EnumJsonAdapter adapter = EnumJsonAdapter.create(Roshambo.class) 64 | .withUnknownFallback(null); 65 | JsonReader reader = JsonReader.of(new Buffer().writeUtf8("\"SPOCK\"")); 66 | assertThat(adapter.fromJson(reader)).isNull(); 67 | assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); 68 | } 69 | 70 | enum Roshambo { 71 | ROCK, 72 | PAPER, 73 | @Json(name = "scr") SCISSORS 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /adapters/src/test/java/com/squareup/moshi/adapters/Rfc3339DateJsonAdapterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.adapters; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import java.util.Calendar; 20 | import java.util.Date; 21 | import java.util.GregorianCalendar; 22 | import java.util.TimeZone; 23 | import java.util.concurrent.TimeUnit; 24 | import org.junit.Test; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | 28 | public final class Rfc3339DateJsonAdapterTest { 29 | private final JsonAdapter adapter = new Rfc3339DateJsonAdapter().lenient(); 30 | 31 | @Test public void fromJsonWithTwoDigitMillis() throws Exception { 32 | assertThat(adapter.fromJson("\"1985-04-12T23:20:50.52Z\"")) 33 | .isEqualTo(newDate(1985, 4, 12, 23, 20, 50, 520, 0)); 34 | } 35 | 36 | @Test public void fromJson() throws Exception { 37 | assertThat(adapter.fromJson("\"1970-01-01T00:00:00.000Z\"")) 38 | .isEqualTo(newDate(1970, 1, 1, 0, 0, 0, 0, 0)); 39 | assertThat(adapter.fromJson("\"1985-04-12T23:20:50.520Z\"")) 40 | .isEqualTo(newDate(1985, 4, 12, 23, 20, 50, 520, 0)); 41 | assertThat(adapter.fromJson("\"1996-12-19T16:39:57-08:00\"")) 42 | .isEqualTo(newDate(1996, 12, 19, 16, 39, 57, 0, -8 * 60)); 43 | assertThat(adapter.fromJson("\"1990-12-31T23:59:60Z\"")) 44 | .isEqualTo(newDate(1990, 12, 31, 23, 59, 59, 0, 0)); 45 | assertThat(adapter.fromJson("\"1990-12-31T15:59:60-08:00\"")) 46 | .isEqualTo(newDate(1990, 12, 31, 15, 59, 59, 0, -8 * 60)); 47 | assertThat(adapter.fromJson("\"1937-01-01T12:00:27.870+00:20\"")) 48 | .isEqualTo(newDate(1937, 1, 1, 12, 0, 27, 870, 20)); 49 | } 50 | 51 | @Test public void toJson() throws Exception { 52 | assertThat(adapter.toJson(newDate(1970, 1, 1, 0, 0, 0, 0, 0))) 53 | .isEqualTo("\"1970-01-01T00:00:00.000Z\""); 54 | assertThat(adapter.toJson(newDate(1985, 4, 12, 23, 20, 50, 520, 0))) 55 | .isEqualTo("\"1985-04-12T23:20:50.520Z\""); 56 | assertThat(adapter.toJson(newDate(1996, 12, 19, 16, 39, 57, 0, -8 * 60))) 57 | .isEqualTo("\"1996-12-20T00:39:57.000Z\""); 58 | assertThat(adapter.toJson(newDate(1990, 12, 31, 23, 59, 59, 0, 0))) 59 | .isEqualTo("\"1990-12-31T23:59:59.000Z\""); 60 | assertThat(adapter.toJson(newDate(1990, 12, 31, 15, 59, 59, 0, -8 * 60))) 61 | .isEqualTo("\"1990-12-31T23:59:59.000Z\""); 62 | assertThat(adapter.toJson(newDate(1937, 1, 1, 12, 0, 27, 870, 20))) 63 | .isEqualTo("\"1937-01-01T11:40:27.870Z\""); 64 | } 65 | 66 | @Test public void nullSafety() throws Exception { 67 | assertThat(adapter.toJson(null)).isEqualTo("null"); 68 | assertThat(adapter.fromJson("null")).isNull(); 69 | } 70 | 71 | private Date newDate( 72 | int year, int month, int day, int hour, int minute, int second, int millis, int offset) { 73 | Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 74 | calendar.set(year, month - 1, day, hour, minute, second); 75 | calendar.set(Calendar.MILLISECOND, millis); 76 | return new Date(calendar.getTimeInMillis() - TimeUnit.MINUTES.toMillis(offset)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /deploy_javadoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | REPO="git@github.com:square/moshi.git" 6 | GROUP_ID="com.squareup.moshi" 7 | ARTIFACT_ID="moshi" 8 | 9 | DIR=temp-clone 10 | 11 | # Delete any existing temporary website clone 12 | rm -rf $DIR 13 | 14 | # Clone the current repo into temp folder 15 | git clone $REPO $DIR 16 | 17 | # Move working directory into temp folder 18 | cd $DIR 19 | 20 | # Checkout and track the gh-pages branch 21 | git checkout -t origin/gh-pages 22 | 23 | # Delete everything 24 | rm -rf * 25 | 26 | # Download the latest javadoc 27 | curl -L "https://search.maven.org/remote_content?g=$GROUP_ID&a=$ARTIFACT_ID&v=LATEST&c=javadoc" > javadoc.zip 28 | unzip javadoc.zip 29 | rm javadoc.zip 30 | 31 | # Stage all files in git and create a commit 32 | git add . 33 | git add -u 34 | git commit -m "Website at $(date)" 35 | 36 | # Push the new files up to GitHub 37 | git push origin gh-pages 38 | 39 | # Delete our temp folder 40 | cd .. 41 | rm -rf $DIR 42 | -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.squareup.moshi 8 | moshi-parent 9 | 1.10.0-SNAPSHOT 10 | 11 | 12 | moshi-examples 13 | 14 | 15 | 16 | com.google.code.findbugs 17 | jsr305 18 | provided 19 | 20 | 21 | com.squareup.moshi 22 | moshi 23 | ${project.version} 24 | 25 | 26 | com.squareup.moshi 27 | moshi-adapters 28 | ${project.version} 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/ByteStrings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.JsonReader; 20 | import com.squareup.moshi.JsonWriter; 21 | import com.squareup.moshi.Moshi; 22 | import java.io.IOException; 23 | import okio.ByteString; 24 | 25 | public final class ByteStrings { 26 | public void run() throws Exception { 27 | String json = "\"TW9zaGksIE9saXZlLCBXaGl0ZSBDaGluPw\""; 28 | 29 | Moshi moshi = new Moshi.Builder() 30 | .add(ByteString.class, new Base64ByteStringAdapter()) 31 | .build(); 32 | JsonAdapter jsonAdapter = moshi.adapter(ByteString.class); 33 | 34 | ByteString byteString = jsonAdapter.fromJson(json); 35 | System.out.println(byteString); 36 | } 37 | 38 | /** 39 | * Formats byte strings using Base64. No line 40 | * breaks or whitespace is included in the encoded form. 41 | */ 42 | public final class Base64ByteStringAdapter extends JsonAdapter { 43 | @Override public ByteString fromJson(JsonReader reader) throws IOException { 44 | String base64 = reader.nextString(); 45 | return ByteString.decodeBase64(base64); 46 | } 47 | 48 | @Override public void toJson(JsonWriter writer, ByteString value) throws IOException { 49 | String string = value.base64(); 50 | writer.value(string); 51 | } 52 | } 53 | 54 | public static void main(String[] args) throws Exception { 55 | new ByteStrings().run(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/CardAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.FromJson; 19 | import com.squareup.moshi.JsonDataException; 20 | import com.squareup.moshi.ToJson; 21 | import com.squareup.moshi.recipes.models.Card; 22 | import com.squareup.moshi.recipes.models.Suit; 23 | 24 | public final class CardAdapter { 25 | @ToJson String toJson(Card card) { 26 | return card.rank + card.suit.name().substring(0, 1); 27 | } 28 | 29 | @FromJson Card fromJson(String card) { 30 | if (card.length() != 2) throw new JsonDataException("Unknown card: " + card); 31 | 32 | char rank = card.charAt(0); 33 | switch (card.charAt(1)) { 34 | case 'C': return new Card(rank, Suit.CLUBS); 35 | case 'D': return new Card(rank, Suit.DIAMONDS); 36 | case 'H': return new Card(rank, Suit.HEARTS); 37 | case 'S': return new Card(rank, Suit.SPADES); 38 | default: throw new JsonDataException("unknown suit: " + card); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/CustomAdapterFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.JsonReader; 20 | import com.squareup.moshi.JsonWriter; 21 | import com.squareup.moshi.Moshi; 22 | import com.squareup.moshi.Types; 23 | import java.io.IOException; 24 | import java.lang.annotation.Annotation; 25 | import java.lang.reflect.ParameterizedType; 26 | import java.lang.reflect.Type; 27 | import java.util.Set; 28 | import java.util.SortedSet; 29 | import java.util.TreeSet; 30 | import javax.annotation.Nullable; 31 | 32 | public final class CustomAdapterFactory { 33 | public void run() throws Exception { 34 | Moshi moshi = new Moshi.Builder() 35 | .add(new SortedSetAdapterFactory()) 36 | .build(); 37 | JsonAdapter> jsonAdapter = moshi.adapter( 38 | Types.newParameterizedType(SortedSet.class, String.class)); 39 | 40 | TreeSet model = new TreeSet<>(); 41 | model.add("a"); 42 | model.add("b"); 43 | model.add("c"); 44 | 45 | String json = jsonAdapter.toJson(model); 46 | System.out.println(json); 47 | } 48 | 49 | /** 50 | * This class composes an adapter for any element type into an adapter for a sorted set of those 51 | * elements. For example, given a {@code JsonAdapter}, use this to get a 52 | * {@code JsonAdapter>}. It works by looping over the input elements when 53 | * both reading and writing. 54 | */ 55 | static final class SortedSetAdapter extends JsonAdapter> { 56 | private final JsonAdapter elementAdapter; 57 | 58 | SortedSetAdapter(JsonAdapter elementAdapter) { 59 | this.elementAdapter = elementAdapter; 60 | } 61 | 62 | @Override public SortedSet fromJson(JsonReader reader) throws IOException { 63 | TreeSet result = new TreeSet<>(); 64 | reader.beginArray(); 65 | while (reader.hasNext()) { 66 | result.add(elementAdapter.fromJson(reader)); 67 | } 68 | reader.endArray(); 69 | return result; 70 | } 71 | 72 | @Override public void toJson(JsonWriter writer, SortedSet set) throws IOException { 73 | writer.beginArray(); 74 | for (T element : set) { 75 | elementAdapter.toJson(writer, element); 76 | } 77 | writer.endArray(); 78 | } 79 | } 80 | 81 | /** 82 | * Moshi asks this class to create JSON adapters. It only knows how to create JSON adapters for 83 | * {@code SortedSet} types, so it returns null for all other requests. When it does get a request 84 | * for a {@code SortedSet}, it asks Moshi for an adapter of the element type {@code X} and then 85 | * uses that to create an adapter for the set. 86 | */ 87 | static class SortedSetAdapterFactory implements JsonAdapter.Factory { 88 | @Override public @Nullable JsonAdapter create( 89 | Type type, Set annotations, Moshi moshi) { 90 | if (!annotations.isEmpty()) { 91 | return null; // Annotations? This factory doesn't apply. 92 | } 93 | 94 | if (!(type instanceof ParameterizedType)) { 95 | return null; // No type parameter? This factory doesn't apply. 96 | } 97 | 98 | ParameterizedType parameterizedType = (ParameterizedType) type; 99 | if (parameterizedType.getRawType() != SortedSet.class) { 100 | return null; // Not a sorted set? This factory doesn't apply. 101 | } 102 | 103 | Type elementType = parameterizedType.getActualTypeArguments()[0]; 104 | JsonAdapter elementAdapter = moshi.adapter(elementType); 105 | 106 | return new SortedSetAdapter<>(elementAdapter).nullSafe(); 107 | } 108 | } 109 | 110 | public static void main(String[] args) throws Exception { 111 | new CustomAdapterFactory().run(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/CustomAdapterWithDelegate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | 19 | import com.squareup.moshi.FromJson; 20 | import com.squareup.moshi.Json; 21 | import com.squareup.moshi.JsonAdapter; 22 | import com.squareup.moshi.Moshi; 23 | import com.squareup.moshi.JsonReader; 24 | 25 | import javax.annotation.Nullable; 26 | import java.io.IOException; 27 | 28 | public final class CustomAdapterWithDelegate { 29 | public void run() throws Exception { 30 | // We want to match any Stage that starts with 'in-progress' as Stage.IN_PROGRESS 31 | // and leave the rest of the enum values as to match as normal. 32 | Moshi moshi = new Moshi.Builder().add(new StageAdapter()).build(); 33 | JsonAdapter jsonAdapter = moshi.adapter(Stage.class); 34 | 35 | System.out.println(jsonAdapter.fromJson("\"not-started\"")); 36 | System.out.println(jsonAdapter.fromJson("\"in-progress\"")); 37 | System.out.println(jsonAdapter.fromJson("\"in-progress-step1\"")); 38 | } 39 | 40 | public static void main(String[] args) throws Exception { 41 | new CustomAdapterWithDelegate().run(); 42 | } 43 | 44 | private enum Stage { 45 | @Json(name = "not-started") NOT_STARTED, 46 | @Json(name = "in-progress") IN_PROGRESS, 47 | @Json(name = "rejected") REJECTED, 48 | @Json(name = "completed") COMPLETED 49 | } 50 | 51 | private static final class StageAdapter { 52 | @FromJson 53 | @Nullable 54 | Stage fromJson(JsonReader jsonReader, JsonAdapter delegate) throws IOException { 55 | String value = jsonReader.nextString(); 56 | 57 | Stage stage; 58 | if (value.startsWith("in-progress")) { 59 | stage = Stage.IN_PROGRESS; 60 | } else { 61 | stage = delegate.fromJsonValue(value); 62 | } 63 | return stage; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/CustomFieldName.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.Moshi; 20 | import com.squareup.moshi.recipes.models.Player; 21 | 22 | public final class CustomFieldName { 23 | public void run() throws Exception { 24 | String json = "" 25 | + "{" 26 | + " \"username\": \"jesse\"," 27 | + " \"lucky number\": 32" 28 | + "}\n"; 29 | 30 | Moshi moshi = new Moshi.Builder().build(); 31 | JsonAdapter jsonAdapter = moshi.adapter(Player.class); 32 | 33 | Player player = jsonAdapter.fromJson(json); 34 | System.out.println(player); 35 | } 36 | 37 | public static void main(String[] args) throws Exception { 38 | new CustomFieldName().run(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/CustomQualifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.FromJson; 19 | import com.squareup.moshi.JsonAdapter; 20 | import com.squareup.moshi.JsonQualifier; 21 | import com.squareup.moshi.Moshi; 22 | import com.squareup.moshi.ToJson; 23 | import java.lang.annotation.Retention; 24 | 25 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 26 | 27 | public final class CustomQualifier { 28 | public void run() throws Exception { 29 | String json = "" 30 | + "{\n" 31 | + " \"color\": \"#ff0000\",\n" 32 | + " \"height\": 768,\n" 33 | + " \"width\": 1024\n" 34 | + "}\n"; 35 | 36 | Moshi moshi = new Moshi.Builder() 37 | .add(new ColorAdapter()) 38 | .build(); 39 | JsonAdapter jsonAdapter = moshi.adapter(Rectangle.class); 40 | 41 | Rectangle rectangle = jsonAdapter.fromJson(json); 42 | System.out.println(rectangle); 43 | } 44 | 45 | public static void main(String[] args) throws Exception { 46 | new CustomQualifier().run(); 47 | } 48 | 49 | static class Rectangle { 50 | int width; 51 | int height; 52 | @HexColor int color; 53 | 54 | @Override public String toString() { 55 | return String.format("%dx%d #%06x", width, height, color); 56 | } 57 | } 58 | 59 | @Retention(RUNTIME) 60 | @JsonQualifier 61 | public @interface HexColor { 62 | } 63 | 64 | static class ColorAdapter { 65 | @ToJson String toJson(@HexColor int rgb) { 66 | return String.format("#%06x", rgb); 67 | } 68 | 69 | @FromJson @HexColor int fromJson(String rgb) { 70 | return Integer.parseInt(rgb.substring(1), 16); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/CustomTypeAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.Moshi; 20 | import com.squareup.moshi.recipes.models.BlackjackHand; 21 | 22 | public final class CustomTypeAdapter { 23 | public void run() throws Exception { 24 | String json = "" 25 | + "{\n" 26 | + " \"hidden_card\": \"6S\",\n" 27 | + " \"visible_cards\": [\n" 28 | + " \"4C\",\n" 29 | + " \"AH\"\n" 30 | + " ]\n" 31 | + "}\n"; 32 | 33 | Moshi moshi = new Moshi.Builder() 34 | .add(new CardAdapter()) 35 | .build(); 36 | JsonAdapter jsonAdapter = moshi.adapter(BlackjackHand.class); 37 | 38 | BlackjackHand blackjackHand = jsonAdapter.fromJson(json); 39 | System.out.println(blackjackHand); 40 | } 41 | 42 | public static void main(String[] args) throws Exception { 43 | new CustomTypeAdapter().run(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/DefaultOnDataMismatchAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.JsonDataException; 20 | import com.squareup.moshi.JsonReader; 21 | import com.squareup.moshi.JsonWriter; 22 | import com.squareup.moshi.Moshi; 23 | import java.io.IOException; 24 | import java.lang.annotation.Annotation; 25 | import java.lang.reflect.Type; 26 | import java.util.Set; 27 | import javax.annotation.Nullable; 28 | 29 | public final class DefaultOnDataMismatchAdapter extends JsonAdapter { 30 | private final JsonAdapter delegate; 31 | private final T defaultValue; 32 | 33 | private DefaultOnDataMismatchAdapter(JsonAdapter delegate, T defaultValue) { 34 | this.delegate = delegate; 35 | this.defaultValue = defaultValue; 36 | } 37 | 38 | @Override public T fromJson(JsonReader reader) throws IOException { 39 | // Use a peeked reader to leave the reader in a known state even if there's an exception. 40 | JsonReader peeked = reader.peekJson(); 41 | T result; 42 | try { 43 | // Attempt to decode to the target type with the peeked reader. 44 | result = delegate.fromJson(peeked); 45 | } catch (JsonDataException e) { 46 | result = defaultValue; 47 | } finally { 48 | peeked.close(); 49 | } 50 | // Skip the value back on the reader, no matter the state of the peeked reader. 51 | reader.skipValue(); 52 | return result; 53 | } 54 | 55 | @Override public void toJson(JsonWriter writer, T value) throws IOException { 56 | delegate.toJson(writer, value); 57 | } 58 | 59 | public static Factory newFactory(final Class type, final T defaultValue) { 60 | return new Factory() { 61 | @Override public @Nullable JsonAdapter create( 62 | Type requestedType, Set annotations, Moshi moshi) { 63 | if (type != requestedType) return null; 64 | JsonAdapter delegate = moshi.nextAdapter(this, type, annotations); 65 | return new DefaultOnDataMismatchAdapter<>(delegate, defaultValue); 66 | } 67 | }; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/FallbackEnum.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.Json; 19 | import com.squareup.moshi.JsonAdapter; 20 | import com.squareup.moshi.JsonQualifier; 21 | import com.squareup.moshi.JsonReader; 22 | import com.squareup.moshi.JsonWriter; 23 | import com.squareup.moshi.Moshi; 24 | import com.squareup.moshi.Types; 25 | import java.io.IOException; 26 | import java.lang.annotation.Annotation; 27 | import java.lang.annotation.Retention; 28 | import java.lang.reflect.Type; 29 | import java.util.Set; 30 | import javax.annotation.Nullable; 31 | 32 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 33 | 34 | final class FallbackEnum { 35 | @Retention(RUNTIME) 36 | @JsonQualifier 37 | public @interface Fallback { 38 | /** 39 | * The enum name. 40 | */ 41 | String value(); 42 | } 43 | 44 | public static final class FallbackEnumJsonAdapter> extends JsonAdapter { 45 | public static final Factory FACTORY = new Factory() { 46 | @Nullable @Override @SuppressWarnings("unchecked") 47 | public JsonAdapter create(Type type, Set annotations, Moshi moshi) { 48 | Class rawType = Types.getRawType(type); 49 | if (!rawType.isEnum()) { 50 | return null; 51 | } 52 | if (annotations.size() != 1) { 53 | return null; 54 | } 55 | Annotation annotation = annotations.iterator().next(); 56 | if (!(annotation instanceof Fallback)) { 57 | return null; 58 | } 59 | Class enumType = (Class) rawType; 60 | Enum fallback = Enum.valueOf(enumType, ((Fallback) annotation).value()); 61 | return new FallbackEnumJsonAdapter<>(enumType, fallback); 62 | } 63 | }; 64 | 65 | final Class enumType; 66 | final String[] nameStrings; 67 | final T[] constants; 68 | final JsonReader.Options options; 69 | final T defaultValue; 70 | 71 | FallbackEnumJsonAdapter(Class enumType, T defaultValue) { 72 | this.enumType = enumType; 73 | this.defaultValue = defaultValue; 74 | try { 75 | constants = enumType.getEnumConstants(); 76 | nameStrings = new String[constants.length]; 77 | for (int i = 0; i < constants.length; i++) { 78 | T constant = constants[i]; 79 | Json annotation = enumType.getField(constant.name()).getAnnotation(Json.class); 80 | String name = annotation != null ? annotation.name() : constant.name(); 81 | nameStrings[i] = name; 82 | } 83 | options = JsonReader.Options.of(nameStrings); 84 | } catch (NoSuchFieldException e) { 85 | throw new AssertionError(e); 86 | } 87 | } 88 | 89 | @Override public T fromJson(JsonReader reader) throws IOException { 90 | int index = reader.selectString(options); 91 | if (index != -1) return constants[index]; 92 | reader.nextString(); 93 | return defaultValue; 94 | } 95 | 96 | @Override public void toJson(JsonWriter writer, T value) throws IOException { 97 | writer.value(nameStrings[value.ordinal()]); 98 | } 99 | 100 | @Override public String toString() { 101 | return "JsonAdapter(" + enumType.getName() + ").defaultValue( " + defaultValue + ")"; 102 | } 103 | } 104 | 105 | static final class Example { 106 | enum Transportation { 107 | WALKING, BIKING, TRAINS, PLANES 108 | } 109 | 110 | @Fallback("WALKING") final Transportation transportation; 111 | 112 | Example(Transportation transportation) { 113 | this.transportation = transportation; 114 | } 115 | 116 | @Override public String toString() { 117 | return transportation.toString(); 118 | } 119 | } 120 | 121 | public static void main(String[] args) throws Exception { 122 | Moshi moshi = new Moshi.Builder() 123 | .add(FallbackEnumJsonAdapter.FACTORY) 124 | .build(); 125 | JsonAdapter adapter = moshi.adapter(Example.class); 126 | System.out.println(adapter.fromJson("{\"transportation\":\"CARS\"}")); 127 | } 128 | 129 | private FallbackEnum() { 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/FromJsonWithoutStrings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.FromJson; 19 | import com.squareup.moshi.JsonAdapter; 20 | import com.squareup.moshi.Moshi; 21 | import com.squareup.moshi.ToJson; 22 | 23 | public final class FromJsonWithoutStrings { 24 | public void run() throws Exception { 25 | // For some reason our JSON has date and time as separate fields. We will clean that up during 26 | // parsing: Moshi will first parse the JSON directly to an EventJson and from that the 27 | // EventJsonAdapter will create the actual Event. 28 | String json = "" 29 | + "{\n" 30 | + " \"title\": \"Blackjack tournament\",\n" 31 | + " \"begin_date\": \"20151010\",\n" 32 | + " \"begin_time\": \"17:04\"\n" 33 | + "}\n"; 34 | 35 | Moshi moshi = new Moshi.Builder().add(new EventJsonAdapter()).build(); 36 | JsonAdapter jsonAdapter = moshi.adapter(Event.class); 37 | 38 | Event event = jsonAdapter.fromJson(json); 39 | System.out.println(event); 40 | System.out.println(jsonAdapter.toJson(event)); 41 | } 42 | 43 | public static void main(String[] args) throws Exception { 44 | new FromJsonWithoutStrings().run(); 45 | } 46 | 47 | @SuppressWarnings("checkstyle:membername") 48 | private static final class EventJson { 49 | String title; 50 | String begin_date; 51 | String begin_time; 52 | } 53 | 54 | public static final class Event { 55 | String title; 56 | String beginDateAndTime; 57 | 58 | @Override public String toString() { 59 | return "Event{" 60 | + "title='" + title + '\'' 61 | + ", beginDateAndTime='" + beginDateAndTime + '\'' 62 | + '}'; 63 | } 64 | } 65 | 66 | private static final class EventJsonAdapter { 67 | @FromJson Event eventFromJson(EventJson eventJson) { 68 | Event event = new Event(); 69 | event.title = eventJson.title; 70 | event.beginDateAndTime = eventJson.begin_date + " " + eventJson.begin_time; 71 | return event; 72 | } 73 | 74 | @ToJson EventJson eventToJson(Event event) { 75 | EventJson json = new EventJson(); 76 | json.title = event.title; 77 | json.begin_date = event.beginDateAndTime.substring(0, 8); 78 | json.begin_time = event.beginDateAndTime.substring(9, 14); 79 | return json; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/MultipleFormats.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.FromJson; 19 | import com.squareup.moshi.JsonAdapter; 20 | import com.squareup.moshi.JsonDataException; 21 | import com.squareup.moshi.JsonQualifier; 22 | import com.squareup.moshi.JsonReader; 23 | import com.squareup.moshi.JsonWriter; 24 | import com.squareup.moshi.Moshi; 25 | import com.squareup.moshi.ToJson; 26 | import com.squareup.moshi.recipes.models.Card; 27 | import com.squareup.moshi.recipes.models.Suit; 28 | import java.io.IOException; 29 | import java.lang.annotation.Retention; 30 | import java.lang.annotation.RetentionPolicy; 31 | 32 | public final class MultipleFormats { 33 | public void run() throws Exception { 34 | Moshi moshi = new Moshi.Builder() 35 | .add(new MultipleFormatsCardAdapter()) 36 | .add(new CardStringAdapter()) 37 | .build(); 38 | 39 | JsonAdapter cardAdapter = moshi.adapter(Card.class); 40 | 41 | // Decode cards from one format or the other. 42 | System.out.println(cardAdapter.fromJson("\"5D\"")); 43 | System.out.println(cardAdapter.fromJson("{\"suit\": \"SPADES\", \"rank\": 5}")); 44 | 45 | // Cards are always encoded as strings. 46 | System.out.println(cardAdapter.toJson(new Card('5', Suit.CLUBS))); 47 | } 48 | 49 | /** Handles cards either as strings "5D" or as objects {"suit": "SPADES", "rank": 5}. */ 50 | public final class MultipleFormatsCardAdapter { 51 | @ToJson void toJson(JsonWriter writer, Card value, 52 | @CardString JsonAdapter stringAdapter) throws IOException { 53 | stringAdapter.toJson(writer, value); 54 | } 55 | 56 | @FromJson Card fromJson(JsonReader reader, @CardString JsonAdapter stringAdapter, 57 | JsonAdapter defaultAdapter) throws IOException { 58 | if (reader.peek() == JsonReader.Token.STRING) { 59 | return stringAdapter.fromJson(reader); 60 | } else { 61 | return defaultAdapter.fromJson(reader); 62 | } 63 | } 64 | } 65 | 66 | /** Handles cards as strings only. */ 67 | public final class CardStringAdapter { 68 | @ToJson String toJson(@CardString Card card) { 69 | return card.rank + card.suit.name().substring(0, 1); 70 | } 71 | 72 | @FromJson @CardString Card fromJson(String card) { 73 | if (card.length() != 2) throw new JsonDataException("Unknown card: " + card); 74 | 75 | char rank = card.charAt(0); 76 | switch (card.charAt(1)) { 77 | case 'C': return new Card(rank, Suit.CLUBS); 78 | case 'D': return new Card(rank, Suit.DIAMONDS); 79 | case 'H': return new Card(rank, Suit.HEARTS); 80 | case 'S': return new Card(rank, Suit.SPADES); 81 | default: throw new JsonDataException("unknown suit: " + card); 82 | } 83 | } 84 | } 85 | 86 | @Retention(RetentionPolicy.RUNTIME) 87 | @JsonQualifier 88 | @interface CardString { 89 | } 90 | 91 | public static void main(String[] args) throws Exception { 92 | new MultipleFormats().run(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/ReadAndWriteRfc3339Dates.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.Moshi; 20 | import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter; 21 | import com.squareup.moshi.recipes.models.Tournament; 22 | import java.util.Calendar; 23 | import java.util.Date; 24 | import java.util.GregorianCalendar; 25 | import java.util.TimeZone; 26 | import java.util.concurrent.TimeUnit; 27 | 28 | public final class ReadAndWriteRfc3339Dates { 29 | public void run() throws Exception { 30 | Moshi moshi = new Moshi.Builder() 31 | .add(Date.class, new Rfc3339DateJsonAdapter()) 32 | .build(); 33 | JsonAdapter jsonAdapter = moshi.adapter(Tournament.class); 34 | 35 | // The RFC3339 JSON adapter can read dates with a timezone offset like '-05:00'. 36 | String lastTournament = "" 37 | + "{" 38 | + " \"location\":\"Chainsaw\"," 39 | + " \"name\":\"21 for 21\"," 40 | + " \"start\":\"2015-09-01T20:00:00-05:00\"" 41 | + "}"; 42 | System.out.println("Last tournament: " + jsonAdapter.fromJson(lastTournament)); 43 | 44 | // The RFC3339 JSON adapter always writes dates with UTC, using a 'Z' suffix. 45 | Tournament nextTournament = new Tournament( 46 | "Waterloo Classic", "Bauer Kitchen", newDate(2015, 10, 1, 20, -5)); 47 | System.out.println("Next tournament JSON: " + jsonAdapter.toJson(nextTournament)); 48 | } 49 | 50 | public static void main(String[] args) throws Exception { 51 | new ReadAndWriteRfc3339Dates().run(); 52 | } 53 | 54 | private Date newDate(int year, int month, int day, int hour, int offset) { 55 | Calendar calendar = new GregorianCalendar(TimeZone.getTimeZone("GMT")); 56 | calendar.set(year, month - 1, day, hour, 0, 0); 57 | calendar.set(Calendar.MILLISECOND, 0); 58 | return new Date(calendar.getTimeInMillis() - TimeUnit.HOURS.toMillis(offset)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/ReadJson.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.Moshi; 20 | import com.squareup.moshi.recipes.models.BlackjackHand; 21 | 22 | public final class ReadJson { 23 | public void run() throws Exception { 24 | String json = "" 25 | + "{\n" 26 | + " \"hidden_card\": {\n" 27 | + " \"rank\": \"6\",\n" 28 | + " \"suit\": \"SPADES\"\n" 29 | + " },\n" 30 | + " \"visible_cards\": [\n" 31 | + " {\n" 32 | + " \"rank\": \"4\",\n" 33 | + " \"suit\": \"CLUBS\"\n" 34 | + " },\n" 35 | + " {\n" 36 | + " \"rank\": \"A\",\n" 37 | + " \"suit\": \"HEARTS\"\n" 38 | + " }\n" 39 | + " ]\n" 40 | + "}\n"; 41 | 42 | Moshi moshi = new Moshi.Builder().build(); 43 | JsonAdapter jsonAdapter = moshi.adapter(BlackjackHand.class); 44 | 45 | BlackjackHand blackjackHand = jsonAdapter.fromJson(json); 46 | System.out.println(blackjackHand); 47 | } 48 | 49 | public static void main(String[] args) throws Exception { 50 | new ReadJson().run(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/ReadJsonList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.Moshi; 20 | import com.squareup.moshi.Types; 21 | import com.squareup.moshi.recipes.models.Card; 22 | import java.lang.reflect.Type; 23 | import java.util.List; 24 | 25 | public final class ReadJsonList { 26 | public void run() throws Exception { 27 | String json = "" 28 | + "[\n" 29 | + " {\n" 30 | + " \"rank\": \"4\",\n" 31 | + " \"suit\": \"CLUBS\"\n" 32 | + " },\n" 33 | + " {\n" 34 | + " \"rank\": \"A\",\n" 35 | + " \"suit\": \"HEARTS\"\n" 36 | + " },\n" 37 | + " {\n" 38 | + " \"rank\": \"J\",\n" 39 | + " \"suit\": \"SPADES\"\n" 40 | + " }\n" 41 | + "]"; 42 | 43 | Moshi moshi = new Moshi.Builder().build(); 44 | 45 | Type listOfCardsType = Types.newParameterizedType(List.class, Card.class); 46 | JsonAdapter> jsonAdapter = moshi.adapter(listOfCardsType); 47 | 48 | List cards = jsonAdapter.fromJson(json); 49 | System.out.println(cards); 50 | } 51 | 52 | public static void main(String[] args) throws Exception { 53 | new ReadJsonList().run(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/RecoverFromTypeMismatch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.Moshi; 20 | import com.squareup.moshi.Types; 21 | import com.squareup.moshi.recipes.models.Suit; 22 | import java.util.List; 23 | 24 | public final class RecoverFromTypeMismatch { 25 | public void run() throws Exception { 26 | String json = "[\"DIAMONDS\", \"STARS\", \"HEARTS\"]"; 27 | 28 | Moshi moshi = new Moshi.Builder() 29 | .add(DefaultOnDataMismatchAdapter.newFactory(Suit.class, Suit.CLUBS)) 30 | .build(); 31 | JsonAdapter> jsonAdapter = moshi.adapter( 32 | Types.newParameterizedType(List.class, Suit.class)); 33 | 34 | List suits = jsonAdapter.fromJson(json); 35 | System.out.println(suits); 36 | } 37 | 38 | public static void main(String[] args) throws Exception { 39 | new RecoverFromTypeMismatch().run(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/Unwrap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.JsonQualifier; 20 | import com.squareup.moshi.JsonReader; 21 | import com.squareup.moshi.JsonWriter; 22 | import com.squareup.moshi.Moshi; 23 | import com.squareup.moshi.Types; 24 | import com.squareup.moshi.recipes.Unwrap.EnvelopeJsonAdapter.Enveloped; 25 | import com.squareup.moshi.recipes.models.Card; 26 | import java.io.IOException; 27 | import java.lang.annotation.Annotation; 28 | import java.lang.annotation.Retention; 29 | import java.lang.reflect.Type; 30 | import java.util.Set; 31 | import javax.annotation.Nullable; 32 | 33 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 34 | 35 | final class Unwrap { 36 | private Unwrap() { 37 | } 38 | 39 | public static void main(String[] args) throws Exception { 40 | String json = "" 41 | + "{\"data\":" 42 | + " {\n" 43 | + " \"rank\": \"4\",\n" 44 | + " \"suit\": \"CLUBS\"\n" 45 | + " }" 46 | + "}"; 47 | Moshi moshi = new Moshi.Builder().add(EnvelopeJsonAdapter.FACTORY).build(); 48 | JsonAdapter adapter = moshi.adapter(Card.class, Enveloped.class); 49 | Card out = adapter.fromJson(json); 50 | System.out.println(out); 51 | } 52 | 53 | public static final class EnvelopeJsonAdapter extends JsonAdapter { 54 | public static final JsonAdapter.Factory FACTORY = new Factory() { 55 | @Override public @Nullable JsonAdapter create( 56 | Type type, Set annotations, Moshi moshi) { 57 | Set delegateAnnotations = 58 | Types.nextAnnotations(annotations, Enveloped.class); 59 | if (delegateAnnotations == null) { 60 | return null; 61 | } 62 | Type envelope = 63 | Types.newParameterizedTypeWithOwner(EnvelopeJsonAdapter.class, Envelope.class, type); 64 | JsonAdapter> delegate = moshi.nextAdapter(this, envelope, delegateAnnotations); 65 | return new EnvelopeJsonAdapter(delegate); 66 | } 67 | }; 68 | 69 | @Retention(RUNTIME) @JsonQualifier public @interface Enveloped { 70 | } 71 | 72 | private static final class Envelope { 73 | final T data; 74 | 75 | Envelope(T data) { 76 | this.data = data; 77 | } 78 | } 79 | 80 | private final JsonAdapter> delegate; 81 | 82 | EnvelopeJsonAdapter(JsonAdapter> delegate) { 83 | this.delegate = delegate; 84 | } 85 | 86 | @Override public Object fromJson(JsonReader reader) throws IOException { 87 | return delegate.fromJson(reader).data; 88 | } 89 | 90 | @Override public void toJson(JsonWriter writer, Object value) throws IOException { 91 | delegate.toJson(writer, new Envelope<>(value)); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/WriteJson.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.Moshi; 20 | import com.squareup.moshi.recipes.models.BlackjackHand; 21 | import com.squareup.moshi.recipes.models.Card; 22 | import java.util.Arrays; 23 | 24 | import static com.squareup.moshi.recipes.models.Suit.CLUBS; 25 | import static com.squareup.moshi.recipes.models.Suit.HEARTS; 26 | import static com.squareup.moshi.recipes.models.Suit.SPADES; 27 | 28 | public final class WriteJson { 29 | public void run() throws Exception { 30 | BlackjackHand blackjackHand = new BlackjackHand( 31 | new Card('6', SPADES), 32 | Arrays.asList(new Card('4', CLUBS), new Card('A', HEARTS))); 33 | 34 | Moshi moshi = new Moshi.Builder().build(); 35 | JsonAdapter jsonAdapter = moshi.adapter(BlackjackHand.class); 36 | 37 | String json = jsonAdapter.toJson(blackjackHand); 38 | System.out.println(json); 39 | } 40 | 41 | public static void main(String[] args) throws Exception { 42 | new WriteJson().run(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/models/BlackjackHand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes.models; 17 | 18 | import java.util.List; 19 | 20 | @SuppressWarnings("checkstyle:membername") 21 | public final class BlackjackHand { 22 | public final Card hidden_card; 23 | public final List visible_cards; 24 | 25 | public BlackjackHand(Card hiddenCard, List visibleCards) { 26 | this.hidden_card = hiddenCard; 27 | this.visible_cards = visibleCards; 28 | } 29 | 30 | @Override public String toString() { 31 | return "hidden=" + hidden_card + ",visible=" + visible_cards; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/models/Card.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes.models; 17 | 18 | public final class Card { 19 | public final char rank; 20 | public final Suit suit; 21 | 22 | public Card(char rank, Suit suit) { 23 | this.rank = rank; 24 | this.suit = suit; 25 | } 26 | 27 | @Override public String toString() { 28 | return String.format("%s%s", rank, suit); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/models/Player.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes.models; 17 | 18 | import com.squareup.moshi.Json; 19 | 20 | public final class Player { 21 | public final String username; 22 | public final @Json(name = "lucky number") int luckyNumber; 23 | 24 | public Player(String username, int luckyNumber) { 25 | this.username = username; 26 | this.luckyNumber = luckyNumber; 27 | } 28 | 29 | @Override public String toString() { 30 | return username + " gets lucky with " + luckyNumber; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/models/Suit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes.models; 17 | 18 | public enum Suit { 19 | CLUBS, DIAMONDS, HEARTS, SPADES; 20 | 21 | @Override public String toString() { 22 | return name().substring(0, 1); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/models/Tournament.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.recipes.models; 17 | 18 | import java.util.Date; 19 | 20 | public final class Tournament { 21 | public final String name; 22 | public final String location; 23 | public final Date start; 24 | 25 | public Tournament(String name, String location, Date start) { 26 | this.name = name; 27 | this.location = location; 28 | this.start = start; 29 | } 30 | 31 | @Override public String toString() { 32 | return name + " at " + location + " on " + start; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/src/main/java/com/squareup/moshi/recipes/package-info.java: -------------------------------------------------------------------------------- 1 | /** Moshi code samples. */ 2 | @javax.annotation.ParametersAreNonnullByDefault 3 | package com.squareup.moshi.recipes; 4 | -------------------------------------------------------------------------------- /kotlin/codegen/src/assembly/dokka.xml: -------------------------------------------------------------------------------- 1 | 5 | javadoc 6 | 7 | jar 8 | 9 | / 10 | 11 | 12 | target/dokka/moshi-kotlin-codegen 13 | / 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/AppliedType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen 17 | 18 | import javax.lang.model.element.TypeElement 19 | import javax.lang.model.type.DeclaredType 20 | import javax.lang.model.util.Types 21 | 22 | /** 23 | * A concrete type like `List` with enough information to know how to resolve its type 24 | * variables. 25 | */ 26 | internal class AppliedType private constructor( 27 | val element: TypeElement, 28 | private val mirror: DeclaredType 29 | ) { 30 | /** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */ 31 | fun supertypes( 32 | types: Types, 33 | result: LinkedHashSet = LinkedHashSet() 34 | ): LinkedHashSet { 35 | result.add(this) 36 | for (supertype in types.directSupertypes(mirror)) { 37 | val supertypeDeclaredType = supertype as DeclaredType 38 | val supertypeElement = supertypeDeclaredType.asElement() as TypeElement 39 | val appliedSupertype = AppliedType(supertypeElement, supertypeDeclaredType) 40 | appliedSupertype.supertypes(types, result) 41 | } 42 | return result 43 | } 44 | 45 | override fun toString() = mirror.toString() 46 | 47 | companion object { 48 | fun get(typeElement: TypeElement): AppliedType { 49 | return AppliedType(typeElement, typeElement.asType() as DeclaredType) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/JsonClassCodegenProcessor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen 17 | 18 | import com.google.auto.service.AutoService 19 | import com.squareup.kotlinpoet.AnnotationSpec 20 | import com.squareup.kotlinpoet.asClassName 21 | import com.squareup.kotlinpoet.classinspector.elements.ElementsClassInspector 22 | import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview 23 | import com.squareup.moshi.JsonClass 24 | import com.squareup.moshi.kotlin.codegen.api.AdapterGenerator 25 | import com.squareup.moshi.kotlin.codegen.api.PropertyGenerator 26 | import net.ltgt.gradle.incap.IncrementalAnnotationProcessor 27 | import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING 28 | import javax.annotation.processing.AbstractProcessor 29 | import javax.annotation.processing.Filer 30 | import javax.annotation.processing.Messager 31 | import javax.annotation.processing.ProcessingEnvironment 32 | import javax.annotation.processing.Processor 33 | import javax.annotation.processing.RoundEnvironment 34 | import javax.lang.model.SourceVersion 35 | import javax.lang.model.element.TypeElement 36 | import javax.lang.model.util.Elements 37 | import javax.lang.model.util.Types 38 | import javax.tools.Diagnostic 39 | 40 | /** 41 | * An annotation processor that reads Kotlin data classes and generates Moshi JsonAdapters for them. 42 | * This generates Kotlin code, and understands basic Kotlin language features like default values 43 | * and companion objects. 44 | * 45 | * The generated class will match the visibility of the given data class (i.e. if it's internal, the 46 | * adapter will also be internal). 47 | */ 48 | @KotlinPoetMetadataPreview 49 | @AutoService(Processor::class) 50 | @IncrementalAnnotationProcessor(ISOLATING) 51 | class JsonClassCodegenProcessor : AbstractProcessor() { 52 | 53 | companion object { 54 | /** 55 | * This annotation processing argument can be specified to have a `@Generated` annotation 56 | * included in the generated code. It is not encouraged unless you need it for static analysis 57 | * reasons and not enabled by default. 58 | * 59 | * Note that this can only be one of the following values: 60 | * * `"javax.annotation.processing.Generated"` (JRE 9+) 61 | * * `"javax.annotation.Generated"` (JRE <9) 62 | */ 63 | const val OPTION_GENERATED = "moshi.generated" 64 | private val POSSIBLE_GENERATED_NAMES = setOf( 65 | "javax.annotation.processing.Generated", 66 | "javax.annotation.Generated" 67 | ) 68 | } 69 | 70 | private lateinit var types: Types 71 | private lateinit var elements: Elements 72 | private lateinit var filer: Filer 73 | private lateinit var messager: Messager 74 | private val annotation = JsonClass::class.java 75 | private var generatedType: TypeElement? = null 76 | 77 | override fun getSupportedAnnotationTypes() = setOf(annotation.canonicalName) 78 | 79 | override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest() 80 | 81 | override fun getSupportedOptions() = setOf(OPTION_GENERATED) 82 | 83 | override fun init(processingEnv: ProcessingEnvironment) { 84 | super.init(processingEnv) 85 | generatedType = processingEnv.options[OPTION_GENERATED]?.let { 86 | require(it in POSSIBLE_GENERATED_NAMES) { 87 | "Invalid option value for $OPTION_GENERATED. Found $it, " + 88 | "allowable values are $POSSIBLE_GENERATED_NAMES." 89 | } 90 | processingEnv.elementUtils.getTypeElement(it) 91 | } 92 | this.types = processingEnv.typeUtils 93 | this.elements = processingEnv.elementUtils 94 | this.filer = processingEnv.filer 95 | this.messager = processingEnv.messager 96 | } 97 | 98 | override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean { 99 | val classInspector = ElementsClassInspector.create(elements, types) 100 | val cachedClassInspector = MoshiCachedClassInspector(classInspector) 101 | for (type in roundEnv.getElementsAnnotatedWith(annotation)) { 102 | if (type !is TypeElement) { 103 | messager.printMessage( 104 | Diagnostic.Kind.ERROR, "@JsonClass can't be applied to $type: must be a Kotlin class", 105 | type) 106 | continue 107 | } 108 | val jsonClass = type.getAnnotation(annotation) 109 | if (jsonClass.generateAdapter && jsonClass.generator.isEmpty()) { 110 | val generator = adapterGenerator(type, cachedClassInspector) ?: continue 111 | generator 112 | .generateFile { 113 | it.toBuilder() 114 | .apply { 115 | generatedType?.asClassName()?.let { generatedClassName -> 116 | addAnnotation( 117 | AnnotationSpec.builder(generatedClassName) 118 | .addMember("value = [%S]", 119 | JsonClassCodegenProcessor::class.java.canonicalName) 120 | .addMember("comments = %S", "https://github.com/square/moshi") 121 | .build() 122 | ) 123 | } 124 | } 125 | .addOriginatingElement(type) 126 | .build() 127 | } 128 | .writeTo(filer) 129 | } 130 | } 131 | 132 | return false 133 | } 134 | 135 | private fun adapterGenerator(element: TypeElement, cachedClassInspector: MoshiCachedClassInspector): AdapterGenerator? { 136 | val type = targetType(messager, elements, types, element, cachedClassInspector) ?: return null 137 | 138 | val properties = mutableMapOf() 139 | for (property in type.properties.values) { 140 | val generator = property.generator(messager, element, elements) 141 | if (generator != null) { 142 | properties[property.name] = generator 143 | } 144 | } 145 | 146 | for ((name, parameter) in type.constructor.parameters) { 147 | if (type.properties[parameter.name] == null && !parameter.hasDefault) { 148 | messager.printMessage( 149 | Diagnostic.Kind.ERROR, 150 | "No property for required constructor parameter $name", 151 | element) 152 | return null 153 | } 154 | } 155 | 156 | // Sort properties so that those with constructor parameters come first. 157 | val sortedProperties = properties.values.sortedBy { 158 | if (it.hasConstructorParameter) { 159 | it.target.parameterIndex 160 | } else { 161 | Integer.MAX_VALUE 162 | } 163 | } 164 | 165 | return AdapterGenerator(type, sortedProperties) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/MoshiCachedClassInspector.kt: -------------------------------------------------------------------------------- 1 | package com.squareup.moshi.kotlin.codegen 2 | 3 | import com.squareup.kotlinpoet.TypeSpec 4 | import com.squareup.kotlinpoet.metadata.ImmutableKmClass 5 | import com.squareup.kotlinpoet.metadata.specs.ClassInspector 6 | import com.squareup.kotlinpoet.metadata.specs.toTypeSpec 7 | import com.squareup.kotlinpoet.metadata.toImmutableKmClass 8 | import javax.lang.model.element.TypeElement 9 | 10 | /** 11 | * This cached API over [ClassInspector] that caches certain lookups Moshi does potentially multiple 12 | * times. This is useful mostly because it avoids duplicate reloads in cases like common base 13 | * classes, common enclosing types, etc. 14 | */ 15 | internal class MoshiCachedClassInspector(private val classInspector: ClassInspector) { 16 | private val elementToSpecCache = mutableMapOf() 17 | private val kmClassToSpecCache = mutableMapOf() 18 | private val metadataToKmClassCache = mutableMapOf() 19 | 20 | fun toImmutableKmClass(metadata: Metadata): ImmutableKmClass { 21 | return metadataToKmClassCache.getOrPut(metadata) { 22 | metadata.toImmutableKmClass() 23 | } 24 | } 25 | 26 | fun toTypeSpec(kmClass: ImmutableKmClass): TypeSpec { 27 | return kmClassToSpecCache.getOrPut(kmClass) { 28 | kmClass.toTypeSpec(classInspector) 29 | } 30 | } 31 | 32 | fun toTypeSpec(element: TypeElement): TypeSpec { 33 | return elementToSpecCache.getOrPut(element) { 34 | toTypeSpec(toImmutableKmClass(element.metadata)) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/DelegateKey.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen.api 17 | 18 | import com.squareup.kotlinpoet.AnnotationSpec 19 | import com.squareup.kotlinpoet.ClassName 20 | import com.squareup.kotlinpoet.KModifier 21 | import com.squareup.kotlinpoet.MemberName 22 | import com.squareup.kotlinpoet.NameAllocator 23 | import com.squareup.kotlinpoet.ParameterSpec 24 | import com.squareup.kotlinpoet.ParameterizedTypeName 25 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 26 | import com.squareup.kotlinpoet.PropertySpec 27 | import com.squareup.kotlinpoet.TypeName 28 | import com.squareup.kotlinpoet.TypeVariableName 29 | import com.squareup.kotlinpoet.WildcardTypeName 30 | import com.squareup.kotlinpoet.asClassName 31 | import com.squareup.kotlinpoet.asTypeName 32 | import com.squareup.moshi.JsonAdapter 33 | import com.squareup.moshi.Types 34 | 35 | /** A JsonAdapter that can be used to encode and decode a particular field. */ 36 | internal data class DelegateKey( 37 | private val type: TypeName, 38 | private val jsonQualifiers: List 39 | ) { 40 | val nullable get() = type.isNullable 41 | 42 | /** Returns an adapter to use when encoding and decoding this property. */ 43 | fun generateProperty( 44 | nameAllocator: NameAllocator, 45 | typeRenderer: TypeRenderer, 46 | moshiParameter: ParameterSpec, 47 | propertyName: String 48 | ): PropertySpec { 49 | val qualifierNames = jsonQualifiers.joinToString("") { 50 | "At${it.className.simpleName}" 51 | } 52 | val adapterName = nameAllocator.newName( 53 | "${type.toVariableName().decapitalize()}${qualifierNames}Adapter", this) 54 | 55 | val adapterTypeName = JsonAdapter::class.asClassName().parameterizedBy(type) 56 | val standardArgs = arrayOf(moshiParameter, 57 | typeRenderer.render(type)) 58 | val (initializerString, args) = when { 59 | jsonQualifiers.isEmpty() -> ", %M()" to arrayOf(MemberName("kotlin.collections", "emptySet")) 60 | else -> { 61 | ", %T.getFieldJsonQualifierAnnotations(javaClass, " + 62 | "%S)" to arrayOf(Types::class.asTypeName(), adapterName) 63 | } 64 | } 65 | val finalArgs = arrayOf(*standardArgs, *args, propertyName) 66 | 67 | return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE) 68 | .addAnnotations(jsonQualifiers) 69 | .initializer("%N.adapter(%L$initializerString, %S)", *finalArgs) 70 | .build() 71 | } 72 | } 73 | 74 | /** 75 | * Returns a suggested variable name derived from a list of type names. This just concatenates, 76 | * yielding types like MapOfStringLong. 77 | */ 78 | private fun List.toVariableNames() = joinToString("") { it.toVariableName() } 79 | 80 | /** Returns a suggested variable name derived from a type name, like nullableListOfString. */ 81 | private fun TypeName.toVariableName(): String { 82 | val base = when (this) { 83 | is ClassName -> simpleName 84 | is ParameterizedTypeName -> rawType.simpleName + "Of" + typeArguments.toVariableNames() 85 | is WildcardTypeName -> (inTypes + outTypes).toVariableNames() 86 | is TypeVariableName -> name + bounds.toVariableNames() 87 | else -> throw IllegalArgumentException("Unrecognized type! $this") 88 | } 89 | 90 | return if (isNullable) { 91 | "Nullable$base" 92 | } else { 93 | base 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/PropertyGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen.api 17 | 18 | import com.squareup.kotlinpoet.BOOLEAN 19 | import com.squareup.kotlinpoet.NameAllocator 20 | import com.squareup.kotlinpoet.PropertySpec 21 | 22 | /** Generates functions to encode and decode a property as JSON. */ 23 | internal class PropertyGenerator( 24 | val target: TargetProperty, 25 | val delegateKey: DelegateKey, 26 | val isTransient: Boolean = false 27 | ) { 28 | val name = target.name 29 | val jsonName = target.jsonName ?: target.name 30 | val hasDefault = target.hasDefault 31 | 32 | lateinit var localName: String 33 | lateinit var localIsPresentName: String 34 | 35 | val isRequired get() = !delegateKey.nullable && !hasDefault 36 | 37 | val hasConstructorParameter get() = target.parameterIndex != -1 38 | 39 | /** 40 | * IsPresent is required if the following conditions are met: 41 | * - Is not transient 42 | * - Has a default 43 | * - Is not a constructor parameter (for constructors we use a defaults mask) 44 | * - Is nullable (because we differentiate absent from null) 45 | * 46 | * This is used to indicate that presence should be checked first before possible assigning null 47 | * to an absent value 48 | */ 49 | val hasLocalIsPresentName = !isTransient && hasDefault && !hasConstructorParameter && delegateKey.nullable 50 | val hasConstructorDefault = hasDefault && hasConstructorParameter 51 | 52 | fun allocateNames(nameAllocator: NameAllocator) { 53 | localName = nameAllocator.newName(name) 54 | localIsPresentName = nameAllocator.newName("${name}Set") 55 | } 56 | 57 | fun generateLocalProperty(): PropertySpec { 58 | return PropertySpec.builder(localName, target.type.copy(nullable = true)) 59 | .mutable(true) 60 | .apply { 61 | if (hasConstructorDefault) { 62 | // We default to the primitive default type, as reflectively invoking the constructor 63 | // without this (even though it's a throwaway) will fail argument type resolution in 64 | // the reflective invocation. 65 | initializer(target.type.defaultPrimitiveValue()) 66 | } else { 67 | initializer("null") 68 | } 69 | } 70 | .build() 71 | } 72 | 73 | fun generateLocalIsPresentProperty(): PropertySpec { 74 | return PropertySpec.builder(localIsPresentName, BOOLEAN) 75 | .mutable(true) 76 | .initializer("false") 77 | .build() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetConstructor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen.api 17 | 18 | import com.squareup.kotlinpoet.KModifier 19 | 20 | /** A constructor in user code that should be called by generated code. */ 21 | internal data class TargetConstructor( 22 | val parameters: LinkedHashMap, 23 | val visibility: KModifier 24 | ) { 25 | init { 26 | visibility.checkIsVisibility() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetParameter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen.api 17 | 18 | import com.squareup.kotlinpoet.AnnotationSpec 19 | import com.squareup.kotlinpoet.TypeName 20 | 21 | /** A parameter in user code that should be populated by generated code. */ 22 | internal data class TargetParameter( 23 | val name: String, 24 | val index: Int, 25 | val type: TypeName, 26 | val hasDefault: Boolean, 27 | val jsonName: String? = null, 28 | val qualifiers: Set? = null 29 | ) 30 | -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetProperty.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen.api 17 | 18 | import com.squareup.kotlinpoet.KModifier 19 | import com.squareup.kotlinpoet.PropertySpec 20 | import com.squareup.kotlinpoet.TypeName 21 | 22 | /** A property in user code that maps to JSON. */ 23 | internal data class TargetProperty( 24 | val propertySpec: PropertySpec, 25 | val parameter: TargetParameter?, 26 | val visibility: KModifier, 27 | val jsonName: String? 28 | ) { 29 | val name: String get() = propertySpec.name 30 | val type: TypeName get() = propertySpec.type 31 | val parameterIndex get() = parameter?.index ?: -1 32 | val hasDefault get() = parameter?.hasDefault ?: true 33 | 34 | override fun toString() = name 35 | } 36 | -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TargetType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen.api 17 | 18 | import com.squareup.kotlinpoet.KModifier 19 | import com.squareup.kotlinpoet.TypeName 20 | import com.squareup.kotlinpoet.TypeVariableName 21 | 22 | /** A user type that should be decoded and encoded by generated code. */ 23 | internal data class TargetType( 24 | val typeName: TypeName, 25 | val constructor: TargetConstructor, 26 | val properties: Map, 27 | val typeVariables: List, 28 | val isDataClass: Boolean, 29 | val visibility: KModifier 30 | ) { 31 | 32 | init { 33 | visibility.checkIsVisibility() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/TypeRenderer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen.api 17 | 18 | import com.squareup.kotlinpoet.ARRAY 19 | import com.squareup.kotlinpoet.BOOLEAN 20 | import com.squareup.kotlinpoet.BYTE 21 | import com.squareup.kotlinpoet.CHAR 22 | import com.squareup.kotlinpoet.ClassName 23 | import com.squareup.kotlinpoet.CodeBlock 24 | import com.squareup.kotlinpoet.DOUBLE 25 | import com.squareup.kotlinpoet.FLOAT 26 | import com.squareup.kotlinpoet.INT 27 | import com.squareup.kotlinpoet.LONG 28 | import com.squareup.kotlinpoet.ParameterizedTypeName 29 | import com.squareup.kotlinpoet.SHORT 30 | import com.squareup.kotlinpoet.TypeName 31 | import com.squareup.kotlinpoet.TypeVariableName 32 | import com.squareup.kotlinpoet.WildcardTypeName 33 | import com.squareup.moshi.Types 34 | 35 | /** 36 | * Renders literals like `Types.newParameterizedType(List::class.java, String::class.java)`. 37 | * Rendering is pluggable so that type variables can either be resolved or emitted as other code 38 | * blocks. 39 | */ 40 | abstract class TypeRenderer { 41 | abstract fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock 42 | 43 | fun render(typeName: TypeName, forceBox: Boolean = false): CodeBlock { 44 | if (typeName.isNullable) { 45 | return renderObjectType(typeName.copy(nullable = false)) 46 | } 47 | 48 | return when (typeName) { 49 | is ClassName -> { 50 | if (forceBox) { 51 | renderObjectType(typeName) 52 | } else { 53 | CodeBlock.of("%T::class.java", typeName) 54 | } 55 | } 56 | 57 | is ParameterizedTypeName -> { 58 | // If it's an Array type, we shortcut this to return Types.arrayOf() 59 | if (typeName.rawType == ARRAY) { 60 | CodeBlock.of("%T.arrayOf(%L)", 61 | Types::class, 62 | renderObjectType(typeName.typeArguments[0])) 63 | } else { 64 | val builder = CodeBlock.builder().apply { 65 | add("%T.", Types::class) 66 | val enclosingClassName = typeName.rawType.enclosingClassName() 67 | if (enclosingClassName != null) { 68 | add("newParameterizedTypeWithOwner(%L, ", render(enclosingClassName)) 69 | } else { 70 | add("newParameterizedType(") 71 | } 72 | add("%T::class.java", typeName.rawType) 73 | for (typeArgument in typeName.typeArguments) { 74 | add(", %L", renderObjectType(typeArgument)) 75 | } 76 | add(")") 77 | } 78 | builder.build() 79 | } 80 | } 81 | 82 | is WildcardTypeName -> { 83 | val target: TypeName 84 | val method: String 85 | when { 86 | typeName.inTypes.size == 1 -> { 87 | target = typeName.inTypes[0] 88 | method = "supertypeOf" 89 | } 90 | typeName.outTypes.size == 1 -> { 91 | target = typeName.outTypes[0] 92 | method = "subtypeOf" 93 | } 94 | else -> throw IllegalArgumentException( 95 | "Unrepresentable wildcard type. Cannot have more than one bound: $typeName") 96 | } 97 | CodeBlock.of("%T.%L(%L)", Types::class, method, render(target, forceBox = true)) 98 | } 99 | 100 | is TypeVariableName -> renderTypeVariable(typeName) 101 | 102 | else -> throw IllegalArgumentException("Unrepresentable type: $typeName") 103 | } 104 | } 105 | 106 | private fun renderObjectType(typeName: TypeName): CodeBlock { 107 | return if (typeName.isPrimitive()) { 108 | CodeBlock.of("%T::class.javaObjectType", typeName) 109 | } else { 110 | render(typeName) 111 | } 112 | } 113 | 114 | private fun TypeName.isPrimitive(): Boolean { 115 | return when (this) { 116 | BOOLEAN, BYTE, SHORT, INT, LONG, CHAR, FLOAT, DOUBLE -> true 117 | else -> false 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /kotlin/codegen/src/main/java/com/squareup/moshi/kotlin/codegen/api/kotlintypes.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen.api 17 | 18 | import com.squareup.kotlinpoet.ANY 19 | import com.squareup.kotlinpoet.ARRAY 20 | import com.squareup.kotlinpoet.BOOLEAN 21 | import com.squareup.kotlinpoet.BYTE 22 | import com.squareup.kotlinpoet.CHAR 23 | import com.squareup.kotlinpoet.ClassName 24 | import com.squareup.kotlinpoet.CodeBlock 25 | import com.squareup.kotlinpoet.DOUBLE 26 | import com.squareup.kotlinpoet.FLOAT 27 | import com.squareup.kotlinpoet.INT 28 | import com.squareup.kotlinpoet.KModifier 29 | import com.squareup.kotlinpoet.LONG 30 | import com.squareup.kotlinpoet.NOTHING 31 | import com.squareup.kotlinpoet.ParameterizedTypeName 32 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 33 | import com.squareup.kotlinpoet.SHORT 34 | import com.squareup.kotlinpoet.STAR 35 | import com.squareup.kotlinpoet.TypeName 36 | import com.squareup.kotlinpoet.TypeVariableName 37 | import com.squareup.kotlinpoet.UNIT 38 | import com.squareup.kotlinpoet.WildcardTypeName 39 | import com.squareup.kotlinpoet.asTypeName 40 | import kotlin.reflect.KClass 41 | 42 | internal fun TypeName.rawType(): ClassName { 43 | return when (this) { 44 | is ClassName -> this 45 | is ParameterizedTypeName -> rawType 46 | else -> throw IllegalArgumentException("Cannot get raw type from $this") 47 | } 48 | } 49 | 50 | internal fun TypeName.defaultPrimitiveValue(): CodeBlock = 51 | when (this) { 52 | BOOLEAN -> CodeBlock.of("false") 53 | CHAR -> CodeBlock.of("0.toChar()") 54 | BYTE -> CodeBlock.of("0.toByte()") 55 | SHORT -> CodeBlock.of("0.toShort()") 56 | INT -> CodeBlock.of("0") 57 | FLOAT -> CodeBlock.of("0f") 58 | LONG -> CodeBlock.of("0L") 59 | DOUBLE -> CodeBlock.of("0.0") 60 | UNIT, Void::class.asTypeName(), NOTHING -> throw IllegalStateException("Parameter with void, Unit, or Nothing type is illegal") 61 | else -> CodeBlock.of("null") 62 | } 63 | 64 | internal fun TypeName.asTypeBlock(): CodeBlock { 65 | when (this) { 66 | is ParameterizedTypeName -> { 67 | return if (rawType == ARRAY) { 68 | CodeBlock.of("%T::class.java", copy(nullable = false)) 69 | } else { 70 | rawType.asTypeBlock() 71 | } 72 | } 73 | is TypeVariableName -> { 74 | val bound = bounds.firstOrNull() ?: ANY 75 | return bound.asTypeBlock() 76 | } 77 | is ClassName -> { 78 | // Check against the non-nullable version for equality, but we'll keep the nullability in 79 | // consideration when creating the CodeBlock if needed. 80 | return when (copy(nullable = false)) { 81 | BOOLEAN, CHAR, BYTE, SHORT, INT, FLOAT, LONG, DOUBLE -> { 82 | if (isNullable) { 83 | // Remove nullable but keep the java object type 84 | CodeBlock.of("%T::class.javaObjectType", copy(nullable = false)) 85 | } else { 86 | CodeBlock.of("%T::class.javaPrimitiveType", this) 87 | } 88 | } 89 | UNIT, Void::class.asTypeName(), NOTHING -> throw IllegalStateException("Parameter with void, Unit, or Nothing type is illegal") 90 | else -> CodeBlock.of("%T::class.java", copy(nullable = false)) 91 | } 92 | } 93 | else -> throw UnsupportedOperationException("Parameter with type '${javaClass.simpleName}' is illegal. Only classes, parameterized types, or type variables are allowed.") 94 | } 95 | } 96 | 97 | internal fun KModifier.checkIsVisibility() { 98 | require(ordinal <= ordinal) { 99 | "Visibility must be one of ${(0..ordinal).joinToString { KModifier.values()[it].name }}. Is $name" 100 | } 101 | } 102 | 103 | internal inline fun TypeName.mapTypes(noinline transform: T.() -> TypeName?): TypeName { 104 | return mapTypes(T::class, transform) 105 | } 106 | 107 | @Suppress("UNCHECKED_CAST") 108 | internal fun TypeName.mapTypes(target: KClass, transform: T.() -> TypeName?): TypeName { 109 | if (target.java == javaClass) { 110 | return (this as T).transform() ?: return this 111 | } 112 | return when (this) { 113 | is ClassName -> this 114 | is ParameterizedTypeName -> { 115 | (rawType.mapTypes(target, transform) as ClassName).parameterizedBy(typeArguments.map { it.mapTypes(target, transform) }) 116 | .copy(nullable = isNullable, annotations = annotations) 117 | } 118 | is TypeVariableName -> { 119 | copy(bounds = bounds.map { it.mapTypes(target, transform) }) 120 | } 121 | is WildcardTypeName -> { 122 | // TODO Would be nice if KotlinPoet modeled these easier. 123 | // Producer type - empty inTypes, single element outTypes 124 | // Consumer type - single element inTypes, single ANY element outType. 125 | when { 126 | this == STAR -> this 127 | outTypes.isNotEmpty() && inTypes.isEmpty() -> { 128 | WildcardTypeName.producerOf(outTypes[0].mapTypes(target, transform)) 129 | .copy(nullable = isNullable, annotations = annotations) 130 | } 131 | inTypes.isNotEmpty() -> { 132 | WildcardTypeName.consumerOf(inTypes[0].mapTypes(target, transform)) 133 | .copy(nullable = isNullable, annotations = annotations) 134 | } 135 | else -> throw UnsupportedOperationException("Not possible.") 136 | } 137 | } 138 | else -> throw UnsupportedOperationException("Type '${javaClass.simpleName}' is illegal. Only classes, parameterized types, wildcard types, or type variables are allowed.") 139 | } 140 | } 141 | 142 | internal fun TypeName.stripTypeVarVariance(): TypeName { 143 | return mapTypes { 144 | TypeVariableName(name = name, bounds = bounds.map { it.mapTypes(TypeVariableName::stripTypeVarVariance) }, variance = null) 145 | } 146 | } -------------------------------------------------------------------------------- /kotlin/codegen/src/test/java/com/squareup/moshi/kotlin/codegen/JavaSuperclass.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen; 17 | 18 | /** For {@link JsonClassCodegenProcessorTest#extendJavaType}. */ 19 | public class JavaSuperclass { 20 | public int a = 1; 21 | } 22 | -------------------------------------------------------------------------------- /kotlin/reflect/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.squareup.moshi 8 | moshi-parent 9 | 1.10.0-SNAPSHOT 10 | ../../pom.xml 11 | 12 | 13 | moshi-kotlin 14 | 15 | 16 | 17 | com.squareup.moshi 18 | moshi 19 | ${project.version} 20 | 21 | 22 | junit 23 | junit 24 | test 25 | 26 | 27 | org.assertj 28 | assertj-core 29 | test 30 | 31 | 32 | org.jetbrains.kotlin 33 | kotlin-stdlib 34 | 35 | 36 | org.jetbrains.kotlin 37 | kotlin-reflect 38 | 39 | 40 | org.jetbrains.kotlin 41 | kotlin-test 42 | test 43 | 44 | 45 | 46 | 47 | true 48 | 49 | 50 | 51 | 52 | 53 | org.jetbrains.kotlin 54 | kotlin-maven-plugin 55 | ${kotlin.version} 56 | 57 | 58 | compile 59 | compile 60 | 61 | compile 62 | 63 | 64 | 65 | test-compile 66 | test-compile 67 | 68 | test-compile 69 | 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-compiler-plugin 76 | 77 | 78 | compile 79 | compile 80 | 81 | compile 82 | 83 | 84 | 85 | testCompile 86 | test-compile 87 | 88 | testCompile 89 | 90 | 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-jar-plugin 96 | 97 | 98 | 99 | com.squareup.moshi.kotlin 100 | 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-assembly-plugin 107 | ${maven-assembly.version} 108 | 109 | 110 | package 111 | 112 | single 113 | 114 | 115 | 116 | 117 | 118 | src/assembly/dokka.xml 119 | 120 | 121 | 122 | 123 | org.jetbrains.dokka 124 | dokka-maven-plugin 125 | ${dokka.version} 126 | 127 | 128 | prepare-package 129 | 130 | dokka 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /kotlin/reflect/src/assembly/dokka.xml: -------------------------------------------------------------------------------- 1 | 5 | javadoc 6 | 7 | jar 8 | 9 | / 10 | 11 | 12 | target/dokka/moshi-kotlin 13 | / 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /kotlin/reflect/src/main/java/com/squareup/moshi/KotlinJsonAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi 17 | 18 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 19 | 20 | @Deprecated( 21 | message = "this moved to avoid a package name conflict in the Java Platform Module System.", 22 | replaceWith = ReplaceWith("com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory") 23 | ) 24 | class KotlinJsonAdapterFactory 25 | : JsonAdapter.Factory by KotlinJsonAdapterFactory() 26 | -------------------------------------------------------------------------------- /kotlin/reflect/src/main/resources/META-INF/proguard/moshi-kotlin.pro: -------------------------------------------------------------------------------- 1 | -keep class kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoaderImpl 2 | 3 | -keepclassmembers class kotlin.Metadata { 4 | public ; 5 | } 6 | -------------------------------------------------------------------------------- /kotlin/reflect/src/main/test/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapterTest.kt: -------------------------------------------------------------------------------- 1 | package com.squareup.moshi.kotlin.reflect 2 | 3 | import com.squareup.moshi.JsonClass 4 | import com.squareup.moshi.Moshi 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.junit.Test 7 | 8 | class KotlinJsonAdapterTest { 9 | @JsonClass(generateAdapter = true) 10 | class Data 11 | 12 | @ExperimentalStdlibApi 13 | @Test 14 | fun fallsBackToReflectiveAdapterWithoutCodegen() { 15 | val moshi = Moshi.Builder() 16 | .add(KotlinJsonAdapterFactory()) 17 | .build() 18 | val adapter = moshi.adapter(Data::class.java) 19 | assertThat(adapter.toString()).isEqualTo( 20 | "KotlinJsonAdapter(com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterTest.Data).nullSafe()" 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /kotlin/tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.squareup.moshi 8 | moshi-parent 9 | 1.10.0-SNAPSHOT 10 | ../../pom.xml 11 | 12 | 13 | moshi-kotlin-tests 14 | 15 | 16 | 17 | com.squareup.moshi 18 | moshi 19 | ${project.version} 20 | 21 | 22 | com.squareup.moshi 23 | moshi-kotlin 24 | ${project.version} 25 | 26 | 27 | org.jetbrains.kotlin 28 | kotlin-stdlib 29 | 30 | 31 | junit 32 | junit 33 | test 34 | 35 | 36 | org.assertj 37 | assertj-core 38 | test 39 | 40 | 41 | 42 | 43 | true 44 | 45 | 46 | 47 | 48 | 49 | kotlin-maven-plugin 50 | org.jetbrains.kotlin 51 | ${kotlin.version} 52 | 53 | 54 | kapt 55 | 56 | kapt 57 | 58 | 59 | 60 | src/main/kotlin 61 | 62 | 63 | 64 | com.squareup.moshi 65 | moshi-kotlin-codegen 66 | ${project.version} 67 | 68 | 69 | 70 | 71 | 72 | compile 73 | 74 | compile 75 | 76 | 77 | 78 | src/main/kotlin 79 | src/main/java 80 | 81 | 82 | 83 | 84 | test-kapt 85 | 86 | test-kapt 87 | 88 | 89 | 90 | src/test/kotlin 91 | src/test/java 92 | 93 | 94 | 95 | com.squareup.moshi 96 | moshi-kotlin-codegen 97 | ${project.version} 98 | 99 | 100 | 101 | 102 | 103 | test-compile 104 | 105 | test-compile 106 | 107 | 108 | 109 | src/test/kotlin 110 | src/test/java 111 | target/generated-sources/kapt/test 112 | 113 | 114 | 115 | 116 | 117 | 118 | -Werror 119 | -Xuse-experimental=kotlin.ExperimentalStdlibApi 120 | -XXLanguage:+InlineClasses 121 | 122 | 123 | 124 | 125 | org.apache.maven.plugins 126 | maven-compiler-plugin 127 | 3.5.1 128 | 129 | none 130 | 1.6 131 | 1.6 132 | 133 | 134 | 135 | 136 | default-compile 137 | none 138 | 139 | 140 | 141 | default-testCompile 142 | none 143 | 144 | 145 | java-compile 146 | compile 147 | 148 | compile 149 | 150 | 151 | 152 | java-test-compile 153 | test-compile 154 | 155 | testCompile 156 | 157 | 158 | 159 | 160 | 161 | org.apache.maven.plugins 162 | maven-assembly-plugin 163 | ${maven-assembly.version} 164 | 165 | 166 | package 167 | 168 | single 169 | 170 | 171 | 172 | 173 | 174 | src/assembly/dokka.xml 175 | 176 | 177 | 178 | 179 | org.jetbrains.dokka 180 | dokka-maven-plugin 181 | ${dokka.version} 182 | 183 | 184 | prepare-package 185 | 186 | dokka 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /kotlin/tests/src/assembly/dokka.xml: -------------------------------------------------------------------------------- 1 | 5 | javadoc 6 | 7 | jar 8 | 9 | / 10 | 11 | 12 | target/dokka/moshi-kotlin-tests 13 | / 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/DefaultConstructorTest.kt: -------------------------------------------------------------------------------- 1 | package com.squareup.moshi.kotlin 2 | 3 | import com.squareup.moshi.JsonClass 4 | import com.squareup.moshi.Moshi 5 | import org.junit.Test 6 | 7 | class DefaultConstructorTest { 8 | 9 | @Test fun minimal() { 10 | val expected = TestClass("requiredClass") 11 | val json = """{"required":"requiredClass"}""" 12 | val instance = Moshi.Builder().build().adapter(TestClass::class.java) 13 | .fromJson(json)!! 14 | check(instance == expected) { 15 | "No match:\nActual : $instance\nExpected: $expected" 16 | } 17 | } 18 | 19 | @Test fun allSet() { 20 | val expected = TestClass("requiredClass", "customOptional", 4, "setDynamic", 5, 6) 21 | val json = """{"required":"requiredClass","optional":"customOptional","optional2":4,"dynamicSelfReferenceOptional":"setDynamic","dynamicOptional":5,"dynamicInlineOptional":6}""" 22 | val instance = Moshi.Builder().build().adapter(TestClass::class.java) 23 | .fromJson(json)!! 24 | check(instance == expected) { 25 | "No match:\nActual : $instance\nExpected: $expected" 26 | } 27 | } 28 | 29 | @Test fun customDynamic() { 30 | val expected = TestClass("requiredClass", "customOptional") 31 | val json = """{"required":"requiredClass","optional":"customOptional"}""" 32 | val instance = Moshi.Builder().build().adapter(TestClass::class.java) 33 | .fromJson(json)!! 34 | check(instance == expected) { 35 | "No match:\nActual : $instance\nExpected: $expected" 36 | } 37 | } 38 | } 39 | 40 | @JsonClass(generateAdapter = true) 41 | data class TestClass( 42 | val required: String, 43 | val optional: String = "optional", 44 | val optional2: Int = 2, 45 | val dynamicSelfReferenceOptional: String = required, 46 | val dynamicOptional: Int = createInt(), 47 | val dynamicInlineOptional: Int = createInlineInt() 48 | ) 49 | 50 | // Regression test for https://github.com/square/moshi/issues/905 51 | // Just needs to compile 52 | @JsonClass(generateAdapter = true) 53 | data class GenericTestClassWithDefaults( 54 | val input: String = "", 55 | val genericInput: T 56 | ) 57 | 58 | private fun createInt(): Int { 59 | return 3 60 | } 61 | 62 | @Suppress("NOTHING_TO_INLINE") 63 | private inline fun createInlineInt(): Int { 64 | return 3 65 | } 66 | -------------------------------------------------------------------------------- /kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen 17 | 18 | import com.squareup.moshi.JsonAdapter 19 | import com.squareup.moshi.JsonReader 20 | import com.squareup.moshi.JsonWriter 21 | import com.squareup.moshi.kotlin.codegen.GeneratedAdaptersTest.CustomGeneratedClass 22 | 23 | // This also tests custom generated types with no moshi constructor 24 | class GeneratedAdaptersTest_CustomGeneratedClassJsonAdapter : JsonAdapter() { 25 | override fun fromJson(reader: JsonReader): CustomGeneratedClass? { 26 | TODO() 27 | } 28 | 29 | override fun toJson(writer: JsonWriter, value: CustomGeneratedClass?) { 30 | TODO() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/LooksLikeAClass/ClassInPackageThatLooksLikeAClass.kt: -------------------------------------------------------------------------------- 1 | package com.squareup.moshi.kotlin.codegen.LooksLikeAClass 2 | 3 | import com.squareup.moshi.JsonClass 4 | 5 | /** 6 | * https://github.com/square/moshi/issues/783 7 | */ 8 | @JsonClass(generateAdapter = true) 9 | data class ClassInPackageThatLooksLikeAClass(val foo: String) 10 | -------------------------------------------------------------------------------- /kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/codegen/MultipleMasksTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.codegen 17 | 18 | import com.squareup.moshi.JsonClass 19 | import com.squareup.moshi.Moshi 20 | import org.intellij.lang.annotations.Language 21 | import org.junit.Assert.assertEquals 22 | import org.junit.Test 23 | 24 | /** 25 | * This test explicitly tests mask generation for classes with more than 32 parameters. Each mask 26 | * can only indicate up to 32 parameters, so constructors with more than 32 parameters have to use 27 | * multiple masks. 28 | * 29 | * This covers a few cases of this: 30 | * - Ensuring values from json are matched to properties correctly 31 | * - Some `@Transient` parameters (which participate in the constructor signature and mask indices) 32 | * - This example has 3 total masks generated. 33 | * 34 | * Regression test for https://github.com/square/moshi/issues/977 35 | */ 36 | class MultipleMasksTest { 37 | @Test fun testMultipleMasks() { 38 | 39 | // Set some arbitrary values to make sure offsets are aligning correctly 40 | @Language("JSON") 41 | val json = """{"arg50":500,"arg3":34,"arg11":11,"arg65":67}""" 42 | 43 | val instance = Moshi.Builder().build().adapter(MultipleMasks::class.java) 44 | .fromJson(json)!! 45 | 46 | assertEquals(instance.arg2, 2) 47 | assertEquals(instance.arg3, 34) 48 | assertEquals(instance.arg11, 11) 49 | assertEquals(instance.arg49, 49) 50 | assertEquals(instance.arg50, 500) 51 | assertEquals(instance.arg65, 67) 52 | assertEquals(instance.arg64, 64) 53 | } 54 | } 55 | 56 | @JsonClass(generateAdapter = true) 57 | class MultipleMasks( 58 | val arg0: Long = 0, 59 | val arg1: Long = 1, 60 | val arg2: Long = 2, 61 | val arg3: Long = 3, 62 | val arg4: Long = 4, 63 | val arg5: Long = 5, 64 | val arg6: Long = 6, 65 | val arg7: Long = 7, 66 | val arg8: Long = 8, 67 | val arg9: Long = 9, 68 | val arg10: Long = 10, 69 | val arg11: Long, 70 | val arg12: Long = 12, 71 | val arg13: Long = 13, 72 | val arg14: Long = 14, 73 | val arg15: Long = 15, 74 | val arg16: Long = 16, 75 | val arg17: Long = 17, 76 | val arg18: Long = 18, 77 | val arg19: Long = 19, 78 | @Suppress("UNUSED_PARAMETER") arg20: Long = 20, 79 | val arg21: Long = 21, 80 | val arg22: Long = 22, 81 | val arg23: Long = 23, 82 | val arg24: Long = 24, 83 | val arg25: Long = 25, 84 | val arg26: Long = 26, 85 | val arg27: Long = 27, 86 | val arg28: Long = 28, 87 | val arg29: Long = 29, 88 | val arg30: Long = 30, 89 | val arg31: Long = 31, 90 | val arg32: Long = 32, 91 | val arg33: Long = 33, 92 | val arg34: Long = 34, 93 | val arg35: Long = 35, 94 | val arg36: Long = 36, 95 | val arg37: Long = 37, 96 | val arg38: Long = 38, 97 | @Transient val arg39: Long = 39, 98 | val arg40: Long = 40, 99 | val arg41: Long = 41, 100 | val arg42: Long = 42, 101 | val arg43: Long = 43, 102 | val arg44: Long = 44, 103 | val arg45: Long = 45, 104 | val arg46: Long = 46, 105 | val arg47: Long = 47, 106 | val arg48: Long = 48, 107 | val arg49: Long = 49, 108 | val arg50: Long = 50, 109 | val arg51: Long = 51, 110 | val arg52: Long = 52, 111 | @Transient val arg53: Long = 53, 112 | val arg54: Long = 54, 113 | val arg55: Long = 55, 114 | val arg56: Long = 56, 115 | val arg57: Long = 57, 116 | val arg58: Long = 58, 117 | val arg59: Long = 59, 118 | val arg60: Long = 60, 119 | val arg61: Long = 61, 120 | val arg62: Long = 62, 121 | val arg63: Long = 63, 122 | val arg64: Long = 64, 123 | val arg65: Long = 65 124 | ) 125 | -------------------------------------------------------------------------------- /kotlin/tests/src/test/kotlin/com/squareup/moshi/kotlin/reflect/-MoshiKotlinExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.kotlin.reflect 17 | 18 | import com.squareup.moshi.JsonAdapter 19 | import com.squareup.moshi.Moshi 20 | import com.squareup.moshi.Types 21 | import com.squareup.moshi.internal.NonNullJsonAdapter 22 | import com.squareup.moshi.internal.NullSafeJsonAdapter 23 | import java.lang.reflect.Type 24 | import kotlin.reflect.KClass 25 | import kotlin.reflect.KType 26 | import kotlin.reflect.KTypeParameter 27 | import kotlin.reflect.KTypeProjection 28 | import kotlin.reflect.KVariance 29 | import kotlin.reflect.typeOf 30 | 31 | /** 32 | * @return a [JsonAdapter] for [T], creating it if necessary. Note that while nullability of [T] 33 | * itself is handled, nested types (such as in generics) are not resolved. 34 | */ 35 | @ExperimentalStdlibApi 36 | inline fun Moshi.adapter(): JsonAdapter { 37 | return adapter(typeOf()) 38 | } 39 | 40 | @ExperimentalStdlibApi 41 | inline fun Moshi.Builder.addAdapter(adapter: JsonAdapter) = add(typeOf().toType(), adapter) 42 | 43 | /** 44 | * @return a [JsonAdapter] for [ktype], creating it if necessary. Note that while nullability of 45 | * [ktype] itself is handled, nested types (such as in generics) are not resolved. 46 | */ 47 | fun Moshi.adapter(ktype: KType): JsonAdapter { 48 | val adapter = adapter(ktype.toType()) 49 | return if (adapter is NullSafeJsonAdapter || adapter is NonNullJsonAdapter) { 50 | // TODO CR - Assume that these know what they're doing? Or should we defensively avoid wrapping for matching nullability? 51 | adapter 52 | } else if (ktype.isMarkedNullable) { 53 | adapter.nullSafe() 54 | } else { 55 | adapter.nonNull() 56 | } 57 | } 58 | 59 | @PublishedApi 60 | internal fun KType.toType(allowPrimitives: Boolean = true): Type { 61 | classifier?.let { 62 | when (it) { 63 | is KTypeParameter -> throw IllegalArgumentException("Type parameters are not supported") 64 | is KClass<*> -> { 65 | val javaType = if (allowPrimitives) { 66 | it.java 67 | } else { 68 | it.javaObjectType 69 | } 70 | if (javaType.isArray) { 71 | return Types.arrayOf(javaType.componentType) 72 | } 73 | 74 | return if (arguments.isEmpty()) { 75 | javaType 76 | } else { 77 | val typeArguments = arguments.toTypedArray { it.toType() } 78 | val enclosingClass = javaType.enclosingClass 79 | return if (enclosingClass != null) { 80 | Types.newParameterizedTypeWithOwner(enclosingClass, javaType, *typeArguments) 81 | } else { 82 | Types.newParameterizedType(javaType, *typeArguments) 83 | } 84 | } 85 | } 86 | else -> throw IllegalArgumentException("Unsupported classifier: $this") 87 | } 88 | } 89 | 90 | // Can happen for intersection types 91 | throw IllegalArgumentException("Unrepresentable type: $this") 92 | } 93 | 94 | internal fun KTypeProjection.toType(): Type { 95 | val javaType = type?.toType(allowPrimitives = false) ?: return Any::class.java 96 | return when (variance) { 97 | null -> Any::class.java 98 | KVariance.INVARIANT -> javaType 99 | KVariance.IN -> Types.subtypeOf(javaType) 100 | KVariance.OUT -> Types.supertypeOf(javaType) 101 | } 102 | } 103 | 104 | private inline fun List.toTypedArray(mapper: (T) -> R): Array { 105 | return Array(size) { 106 | mapper.invoke(get(it)) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /moshi/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | com.squareup.moshi 8 | moshi-parent 9 | 1.10.0-SNAPSHOT 10 | 11 | 12 | moshi 13 | Moshi 14 | 15 | 16 | 17 | com.squareup.okio 18 | okio 19 | 20 | 21 | com.google.code.findbugs 22 | jsr305 23 | provided 24 | 25 | 26 | junit 27 | junit 28 | test 29 | 30 | 31 | org.assertj 32 | assertj-core 33 | test 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-jar-plugin 42 | 43 | 44 | 45 | com.squareup.moshi 46 | 47 | 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-javadoc-plugin 53 | 2.10.4 54 | 55 | com.squareup.moshi.internal:com.squareup.moshi.internal.* 56 | 57 | https://square.github.io/okio/ 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/ArrayJsonAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.io.IOException; 19 | import java.lang.annotation.Annotation; 20 | import java.lang.reflect.Array; 21 | import java.lang.reflect.Type; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.Set; 25 | import javax.annotation.Nullable; 26 | 27 | /** 28 | * Converts arrays to JSON arrays containing their converted contents. This 29 | * supports both primitive and object arrays. 30 | */ 31 | final class ArrayJsonAdapter extends JsonAdapter { 32 | public static final Factory FACTORY = new Factory() { 33 | @Override public @Nullable JsonAdapter create( 34 | Type type, Set annotations, Moshi moshi) { 35 | Type elementType = Types.arrayComponentType(type); 36 | if (elementType == null) return null; 37 | if (!annotations.isEmpty()) return null; 38 | Class elementClass = Types.getRawType(elementType); 39 | JsonAdapter elementAdapter = moshi.adapter(elementType); 40 | return new ArrayJsonAdapter(elementClass, elementAdapter).nullSafe(); 41 | } 42 | }; 43 | 44 | private final Class elementClass; 45 | private final JsonAdapter elementAdapter; 46 | 47 | ArrayJsonAdapter(Class elementClass, JsonAdapter elementAdapter) { 48 | this.elementClass = elementClass; 49 | this.elementAdapter = elementAdapter; 50 | } 51 | 52 | @Override public Object fromJson(JsonReader reader) throws IOException { 53 | List list = new ArrayList<>(); 54 | reader.beginArray(); 55 | while (reader.hasNext()) { 56 | list.add(elementAdapter.fromJson(reader)); 57 | } 58 | reader.endArray(); 59 | Object array = Array.newInstance(elementClass, list.size()); 60 | for (int i = 0; i < list.size(); i++) { 61 | Array.set(array, i, list.get(i)); 62 | } 63 | return array; 64 | } 65 | 66 | @Override public void toJson(JsonWriter writer, Object value) throws IOException { 67 | writer.beginArray(); 68 | for (int i = 0, size = Array.getLength(value); i < size; i++) { 69 | elementAdapter.toJson(writer, Array.get(value, i)); 70 | } 71 | writer.endArray(); 72 | } 73 | 74 | @Override public String toString() { 75 | return elementAdapter + ".array()"; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/ClassFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import com.squareup.moshi.internal.Util; 19 | import java.io.ObjectInputStream; 20 | import java.io.ObjectStreamClass; 21 | import java.lang.reflect.Constructor; 22 | import java.lang.reflect.Field; 23 | import java.lang.reflect.InvocationTargetException; 24 | import java.lang.reflect.Method; 25 | 26 | /** 27 | * Magic that creates instances of arbitrary concrete classes. Derived from Gson's UnsafeAllocator 28 | * and ConstructorConstructor classes. 29 | * 30 | * @author Joel Leitch 31 | * @author Jesse Wilson 32 | */ 33 | abstract class ClassFactory { 34 | abstract T newInstance() throws 35 | InvocationTargetException, IllegalAccessException, InstantiationException; 36 | 37 | public static ClassFactory get(final Class rawType) { 38 | // Try to find a no-args constructor. May be any visibility including private. 39 | try { 40 | final Constructor constructor = rawType.getDeclaredConstructor(); 41 | constructor.setAccessible(true); 42 | return new ClassFactory() { 43 | @SuppressWarnings("unchecked") // T is the same raw type as is requested 44 | @Override public T newInstance() throws IllegalAccessException, InvocationTargetException, 45 | InstantiationException { 46 | Object[] args = null; 47 | return (T) constructor.newInstance(args); 48 | } 49 | @Override public String toString() { 50 | return rawType.getName(); 51 | } 52 | }; 53 | } catch (NoSuchMethodException ignored) { 54 | // No no-args constructor. Fall back to something more magical... 55 | } 56 | 57 | // Try the JVM's Unsafe mechanism. 58 | // public class Unsafe { 59 | // public Object allocateInstance(Class type); 60 | // } 61 | try { 62 | Class unsafeClass = Class.forName("sun.misc.Unsafe"); 63 | Field f = unsafeClass.getDeclaredField("theUnsafe"); 64 | f.setAccessible(true); 65 | final Object unsafe = f.get(null); 66 | final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class); 67 | return new ClassFactory() { 68 | @SuppressWarnings("unchecked") 69 | @Override public T newInstance() throws InvocationTargetException, IllegalAccessException { 70 | return (T) allocateInstance.invoke(unsafe, rawType); 71 | } 72 | @Override public String toString() { 73 | return rawType.getName(); 74 | } 75 | }; 76 | } catch (IllegalAccessException e) { 77 | throw new AssertionError(); 78 | } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException ignored) { 79 | // Not the expected version of the Oracle Java library! 80 | } 81 | 82 | // Try (post-Gingerbread) Dalvik/libcore's ObjectStreamClass mechanism. 83 | // public class ObjectStreamClass { 84 | // private static native int getConstructorId(Class c); 85 | // private static native Object newInstance(Class instantiationClass, int methodId); 86 | // } 87 | try { 88 | Method getConstructorId = ObjectStreamClass.class.getDeclaredMethod( 89 | "getConstructorId", Class.class); 90 | getConstructorId.setAccessible(true); 91 | final int constructorId = (Integer) getConstructorId.invoke(null, Object.class); 92 | final Method newInstance = ObjectStreamClass.class.getDeclaredMethod("newInstance", 93 | Class.class, int.class); 94 | newInstance.setAccessible(true); 95 | return new ClassFactory() { 96 | @SuppressWarnings("unchecked") 97 | @Override public T newInstance() throws InvocationTargetException, IllegalAccessException { 98 | return (T) newInstance.invoke(null, rawType, constructorId); 99 | } 100 | @Override public String toString() { 101 | return rawType.getName(); 102 | } 103 | }; 104 | } catch (IllegalAccessException e) { 105 | throw new AssertionError(); 106 | } catch (InvocationTargetException e) { 107 | throw Util.rethrowCause(e); 108 | } catch (NoSuchMethodException ignored) { 109 | // Not the expected version of Dalvik/libcore! 110 | } 111 | 112 | // Try (pre-Gingerbread) Dalvik/libcore's ObjectInputStream mechanism. 113 | // public class ObjectInputStream { 114 | // private static native Object newInstance( 115 | // Class instantiationClass, Class constructorClass); 116 | // } 117 | try { 118 | final Method newInstance = ObjectInputStream.class.getDeclaredMethod( 119 | "newInstance", Class.class, Class.class); 120 | newInstance.setAccessible(true); 121 | return new ClassFactory() { 122 | @SuppressWarnings("unchecked") 123 | @Override public T newInstance() throws InvocationTargetException, IllegalAccessException { 124 | return (T) newInstance.invoke(null, rawType, Object.class); 125 | } 126 | @Override public String toString() { 127 | return rawType.getName(); 128 | } 129 | }; 130 | } catch (Exception ignored) { 131 | } 132 | 133 | throw new IllegalArgumentException("cannot construct instances of " + rawType.getName()); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/CollectionJsonAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.io.IOException; 19 | import java.lang.annotation.Annotation; 20 | import java.lang.reflect.Type; 21 | import java.util.ArrayList; 22 | import java.util.Collection; 23 | import java.util.LinkedHashSet; 24 | import java.util.List; 25 | import java.util.Set; 26 | import javax.annotation.Nullable; 27 | 28 | /** Converts collection types to JSON arrays containing their converted contents. */ 29 | abstract class CollectionJsonAdapter, T> extends JsonAdapter { 30 | public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() { 31 | @Override public @Nullable JsonAdapter create( 32 | Type type, Set annotations, Moshi moshi) { 33 | Class rawType = Types.getRawType(type); 34 | if (!annotations.isEmpty()) return null; 35 | if (rawType == List.class || rawType == Collection.class) { 36 | return newArrayListAdapter(type, moshi).nullSafe(); 37 | } else if (rawType == Set.class) { 38 | return newLinkedHashSetAdapter(type, moshi).nullSafe(); 39 | } 40 | return null; 41 | } 42 | }; 43 | 44 | private final JsonAdapter elementAdapter; 45 | 46 | private CollectionJsonAdapter(JsonAdapter elementAdapter) { 47 | this.elementAdapter = elementAdapter; 48 | } 49 | 50 | static JsonAdapter> newArrayListAdapter(Type type, Moshi moshi) { 51 | Type elementType = Types.collectionElementType(type, Collection.class); 52 | JsonAdapter elementAdapter = moshi.adapter(elementType); 53 | return new CollectionJsonAdapter, T>(elementAdapter) { 54 | @Override Collection newCollection() { 55 | return new ArrayList<>(); 56 | } 57 | }; 58 | } 59 | 60 | static JsonAdapter> newLinkedHashSetAdapter(Type type, Moshi moshi) { 61 | Type elementType = Types.collectionElementType(type, Collection.class); 62 | JsonAdapter elementAdapter = moshi.adapter(elementType); 63 | return new CollectionJsonAdapter, T>(elementAdapter) { 64 | @Override Set newCollection() { 65 | return new LinkedHashSet<>(); 66 | } 67 | }; 68 | } 69 | 70 | abstract C newCollection(); 71 | 72 | @Override public C fromJson(JsonReader reader) throws IOException { 73 | C result = newCollection(); 74 | reader.beginArray(); 75 | while (reader.hasNext()) { 76 | result.add(elementAdapter.fromJson(reader)); 77 | } 78 | reader.endArray(); 79 | return result; 80 | } 81 | 82 | @Override public void toJson(JsonWriter writer, C value) throws IOException { 83 | writer.beginArray(); 84 | for (T element : value) { 85 | elementAdapter.toJson(writer, element); 86 | } 87 | writer.endArray(); 88 | } 89 | 90 | @Override public String toString() { 91 | return elementAdapter + ".collection()"; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/FromJson.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | @Retention(RetentionPolicy.RUNTIME) 24 | @Target(ElementType.METHOD) 25 | public @interface FromJson { 26 | } 27 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/Json.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 23 | 24 | /** 25 | * Customizes how a field is encoded as JSON. 26 | * 27 | *

Although this annotation doesn't declare a {@link Target}, it is only honored in the following 28 | * elements: 29 | * 30 | *

    31 | *
  • Java class fields 32 | *
  • Kotlin properties for use with {@code moshi-kotlin}. This includes both 33 | * properties declared in the constructor and properties declared as members. 34 | *
35 | * 36 | *

Users of the AutoValue: Moshi 37 | * Extension may also use this annotation on abstract getters. 38 | */ 39 | @Retention(RUNTIME) 40 | @Documented 41 | public @interface Json { 42 | String name(); 43 | } 44 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/JsonClass.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.reflect.Type; 21 | 22 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 23 | 24 | /** 25 | * Customizes how a type is encoded as JSON. 26 | */ 27 | @Retention(RUNTIME) 28 | @Documented 29 | public @interface JsonClass { 30 | /** 31 | * True to trigger the annotation processor to generate an adapter for this type. 32 | * 33 | *

There are currently some restrictions on which types that can be used with generated 34 | * adapters: 35 | *

    36 | *
  • 37 | * The class must be implemented in Kotlin (unless using a custom generator, see 38 | * {@link #generator()}). 39 | *
  • 40 | *
  • The class may not be an abstract class, an inner class, or a local class.
  • 41 | *
  • All superclasses must be implemented in Kotlin.
  • 42 | *
  • All properties must be public, protected, or internal.
  • 43 | *
  • All properties must be either non-transient or have a default value.
  • 44 | *
45 | */ 46 | boolean generateAdapter(); 47 | 48 | /** 49 | * An optional custom generator tag used to indicate which generator should be used. If empty, 50 | * Moshi's annotation processor will generate an adapter for the annotated type. If not empty, 51 | * Moshi's processor will skip it and defer to a custom generator. This can be used to allow 52 | * other custom code generation tools to run and still allow Moshi to read their generated 53 | * JsonAdapter outputs. 54 | * 55 | *

Requirements for generated adapter class signatures: 56 | *

    57 | *
  • 58 | * The generated adapter must subclass {@link JsonAdapter} and be parameterized by this type. 59 | *
  • 60 | *
  • 61 | * {@link Types#generatedJsonAdapterName} should be used for the fully qualified class name in 62 | * order for Moshi to correctly resolve and load the generated JsonAdapter. 63 | *
  • 64 | *
  • The first parameter must be a {@link Moshi} instance.
  • 65 | *
  • 66 | * If generic, a second {@link Type[]} parameter should be declared to accept type arguments. 67 | *
  • 68 | *
69 | * 70 | *

Example for a class "CustomType":

{@code
71 |    *   class CustomTypeJsonAdapter(moshi: Moshi, types: Array) : JsonAdapter() {
72 |    *     // ...
73 |    *   }
74 |    * }
75 | * 76 | *

To help ensure your own generator meets requirements above, you can use Moshi’s built-in 77 | * generator to create the API signature to get started, then make your own generator match that 78 | * expected signature. 79 | */ 80 | String generator() default ""; 81 | } 82 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/JsonDataException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import javax.annotation.Nullable; 19 | 20 | /** 21 | * Thrown when the data in a JSON document doesn't match the data expected by the caller. For 22 | * example, suppose the application expects a boolean but the JSON document contains a string. When 23 | * the call to {@link JsonReader#nextBoolean} is made, a {@code JsonDataException} is thrown. 24 | * 25 | *

Exceptions of this type should be fixed by either changing the application code to accept 26 | * the unexpected JSON, or by changing the JSON to conform to the application's expectations. 27 | * 28 | *

This exception may also be triggered if a document's nesting exceeds 31 levels. This depth is 29 | * sufficient for all practical applications, but shallow enough to avoid uglier failures like 30 | * {@link StackOverflowError}. 31 | */ 32 | public final class JsonDataException extends RuntimeException { 33 | public JsonDataException() { 34 | } 35 | 36 | public JsonDataException(@Nullable String message) { 37 | super(message); 38 | } 39 | 40 | public JsonDataException(@Nullable Throwable cause) { 41 | super(cause); 42 | } 43 | 44 | public JsonDataException(@Nullable String message, @Nullable Throwable cause) { 45 | super(message, cause); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/JsonEncodingException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.io.IOException; 19 | import javax.annotation.Nullable; 20 | 21 | /** Thrown when the data being parsed is not encoded as valid JSON. */ 22 | public final class JsonEncodingException extends IOException { 23 | public JsonEncodingException(@Nullable String message) { 24 | super(message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/JsonQualifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.ANNOTATION_TYPE; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** Annotates another annotation, causing it to specialize how values are encoded and decoded. */ 26 | @Target(ANNOTATION_TYPE) 27 | @Retention(RUNTIME) 28 | @Documented 29 | public @interface JsonQualifier { 30 | } 31 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/JsonScope.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | /** Lexical scoping elements within a JSON reader or writer. */ 19 | final class JsonScope { 20 | private JsonScope() { 21 | } 22 | 23 | /** An array with no elements requires no separators or newlines before it is closed. */ 24 | static final int EMPTY_ARRAY = 1; 25 | 26 | /** A array with at least one value requires a comma and newline before the next element. */ 27 | static final int NONEMPTY_ARRAY = 2; 28 | 29 | /** An object with no name/value pairs requires no separators or newlines before it is closed. */ 30 | static final int EMPTY_OBJECT = 3; 31 | 32 | /** An object whose most recent element is a key. The next element must be a value. */ 33 | static final int DANGLING_NAME = 4; 34 | 35 | /** An object with at least one name/value pair requires a separator before the next element. */ 36 | static final int NONEMPTY_OBJECT = 5; 37 | 38 | /** No object or array has been started. */ 39 | static final int EMPTY_DOCUMENT = 6; 40 | 41 | /** A document with at an array or object. */ 42 | static final int NONEMPTY_DOCUMENT = 7; 43 | 44 | /** A document that's been closed and cannot be accessed. */ 45 | static final int CLOSED = 8; 46 | 47 | /** Sits above the actual state to indicate that a value is currently being streamed in. */ 48 | static final int STREAMING_VALUE = 9; 49 | 50 | /** 51 | * Renders the path in a JSON document to a string. The {@code pathNames} and {@code pathIndices} 52 | * parameters corresponds directly to stack: At indices where the stack contains an object 53 | * (EMPTY_OBJECT, DANGLING_NAME or NONEMPTY_OBJECT), pathNames contains the name at this scope. 54 | * Where it contains an array (EMPTY_ARRAY, NONEMPTY_ARRAY) pathIndices contains the current index 55 | * in that array. Otherwise the value is undefined, and we take advantage of that by incrementing 56 | * pathIndices when doing so isn't useful. 57 | */ 58 | static String getPath(int stackSize, int[] stack, String[] pathNames, int[] pathIndices) { 59 | StringBuilder result = new StringBuilder().append('$'); 60 | for (int i = 0; i < stackSize; i++) { 61 | switch (stack[i]) { 62 | case EMPTY_ARRAY: 63 | case NONEMPTY_ARRAY: 64 | result.append('[').append(pathIndices[i]).append(']'); 65 | break; 66 | 67 | case EMPTY_OBJECT: 68 | case DANGLING_NAME: 69 | case NONEMPTY_OBJECT: 70 | result.append('.'); 71 | if (pathNames[i] != null) { 72 | result.append(pathNames[i]); 73 | } 74 | break; 75 | 76 | case NONEMPTY_DOCUMENT: 77 | case EMPTY_DOCUMENT: 78 | case CLOSED: 79 | break; 80 | } 81 | } 82 | return result.toString(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/MapJsonAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.io.IOException; 19 | import java.lang.annotation.Annotation; 20 | import java.lang.reflect.Type; 21 | import java.util.Map; 22 | import java.util.Set; 23 | import javax.annotation.Nullable; 24 | 25 | /** 26 | * Converts maps with string keys to JSON objects. 27 | * 28 | * TODO: support maps with other key types and convert to/from strings. 29 | */ 30 | final class MapJsonAdapter extends JsonAdapter> { 31 | public static final Factory FACTORY = new Factory() { 32 | @Override public @Nullable JsonAdapter create( 33 | Type type, Set annotations, Moshi moshi) { 34 | if (!annotations.isEmpty()) return null; 35 | Class rawType = Types.getRawType(type); 36 | if (rawType != Map.class) return null; 37 | Type[] keyAndValue = Types.mapKeyAndValueTypes(type, rawType); 38 | return new MapJsonAdapter<>(moshi, keyAndValue[0], keyAndValue[1]).nullSafe(); 39 | } 40 | }; 41 | 42 | private final JsonAdapter keyAdapter; 43 | private final JsonAdapter valueAdapter; 44 | 45 | MapJsonAdapter(Moshi moshi, Type keyType, Type valueType) { 46 | this.keyAdapter = moshi.adapter(keyType); 47 | this.valueAdapter = moshi.adapter(valueType); 48 | } 49 | 50 | @Override public void toJson(JsonWriter writer, Map map) throws IOException { 51 | writer.beginObject(); 52 | for (Map.Entry entry : map.entrySet()) { 53 | if (entry.getKey() == null) { 54 | throw new JsonDataException("Map key is null at " + writer.getPath()); 55 | } 56 | writer.promoteValueToName(); 57 | keyAdapter.toJson(writer, entry.getKey()); 58 | valueAdapter.toJson(writer, entry.getValue()); 59 | } 60 | writer.endObject(); 61 | } 62 | 63 | @Override public Map fromJson(JsonReader reader) throws IOException { 64 | LinkedHashTreeMap result = new LinkedHashTreeMap<>(); 65 | reader.beginObject(); 66 | while (reader.hasNext()) { 67 | reader.promoteNameToValue(); 68 | K name = keyAdapter.fromJson(reader); 69 | V value = valueAdapter.fromJson(reader); 70 | V replaced = result.put(name, value); 71 | if (replaced != null) { 72 | throw new JsonDataException("Map key '" + name + "' has multiple values at path " 73 | + reader.getPath() + ": " + replaced + " and " + value); 74 | } 75 | } 76 | reader.endObject(); 77 | return result; 78 | } 79 | 80 | @Override public String toString() { 81 | return "JsonAdapter(" + keyAdapter + "=" + valueAdapter + ")"; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/ToJson.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | @Retention(RetentionPolicy.RUNTIME) 24 | @Target(ElementType.METHOD) 25 | public @interface ToJson { 26 | } 27 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/internal/NonNullJsonAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.internal; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.JsonDataException; 20 | import com.squareup.moshi.JsonReader; 21 | import com.squareup.moshi.JsonWriter; 22 | import java.io.IOException; 23 | import javax.annotation.Nullable; 24 | 25 | public final class NonNullJsonAdapter extends JsonAdapter { 26 | 27 | private final JsonAdapter delegate; 28 | 29 | public NonNullJsonAdapter(JsonAdapter delegate) { 30 | this.delegate = delegate; 31 | } 32 | 33 | public JsonAdapter delegate() { 34 | return delegate; 35 | } 36 | 37 | @Nullable 38 | @Override 39 | public T fromJson(JsonReader reader) throws IOException { 40 | if (reader.peek() == JsonReader.Token.NULL) { 41 | throw new JsonDataException("Unexpected null at " + reader.getPath()); 42 | } else { 43 | return delegate.fromJson(reader); 44 | } 45 | } 46 | 47 | @Override 48 | public void toJson(JsonWriter writer, @Nullable T value) throws IOException { 49 | if (value == null) { 50 | throw new JsonDataException("Unexpected null at " + writer.getPath()); 51 | } else { 52 | delegate.toJson(writer, value); 53 | } 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return delegate + ".nonNull()"; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/internal/NullSafeJsonAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi.internal; 17 | 18 | import com.squareup.moshi.JsonAdapter; 19 | import com.squareup.moshi.JsonReader; 20 | import com.squareup.moshi.JsonWriter; 21 | import java.io.IOException; 22 | import javax.annotation.Nullable; 23 | 24 | public final class NullSafeJsonAdapter extends JsonAdapter { 25 | 26 | private final JsonAdapter delegate; 27 | 28 | public NullSafeJsonAdapter(JsonAdapter delegate) { 29 | this.delegate = delegate; 30 | } 31 | 32 | public JsonAdapter delegate() { 33 | return delegate; 34 | } 35 | 36 | @Override public @Nullable T fromJson(JsonReader reader) throws IOException { 37 | if (reader.peek() == JsonReader.Token.NULL) { 38 | return reader.nextNull(); 39 | } else { 40 | return delegate.fromJson(reader); 41 | } 42 | } 43 | 44 | @Override public void toJson(JsonWriter writer, @Nullable T value) throws IOException { 45 | if (value == null) { 46 | writer.nullValue(); 47 | } else { 48 | delegate.toJson(writer, value); 49 | } 50 | } 51 | 52 | @Override public String toString() { 53 | return delegate + ".nullSafe()"; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /moshi/src/main/java/com/squareup/moshi/package-info.java: -------------------------------------------------------------------------------- 1 | /** Moshi is modern JSON library for Android and Java. */ 2 | @javax.annotation.ParametersAreNonnullByDefault 3 | package com.squareup.moshi; 4 | -------------------------------------------------------------------------------- /moshi/src/main/resources/META-INF/proguard/moshi.pro: -------------------------------------------------------------------------------- 1 | # JSR 305 annotations are for embedding nullability information. 2 | -dontwarn javax.annotation.** 3 | 4 | -keepclasseswithmembers class * { 5 | @com.squareup.moshi.* ; 6 | } 7 | 8 | -keep @com.squareup.moshi.JsonQualifier interface * 9 | 10 | # Enum field names are used by the integrated EnumJsonAdapter. 11 | # values() is synthesized by the Kotlin compiler and is used by EnumJsonAdapter indirectly 12 | # Annotate enums with @JsonClass(generateAdapter = false) to use them with Moshi. 13 | -keepclassmembers @com.squareup.moshi.JsonClass class * extends java.lang.Enum { 14 | ; 15 | **[] values(); 16 | } 17 | 18 | # The name of @JsonClass types is used to look up the generated adapter. 19 | -keepnames @com.squareup.moshi.JsonClass class * 20 | 21 | # Retain generated target class's synthetic defaults constructor and keep DefaultConstructorMarker's 22 | # name. We will look this up reflectively to invoke the type's constructor. 23 | # 24 | # We can't _just_ keep the defaults constructor because Proguard/R8's spec doesn't allow wildcard 25 | # matching preceding parameters. 26 | -keepnames class kotlin.jvm.internal.DefaultConstructorMarker 27 | -keepclassmembers @com.squareup.moshi.JsonClass @kotlin.Metadata class * { 28 | synthetic (...); 29 | } 30 | 31 | # Retain generated JsonAdapters if annotated type is retained. 32 | -if @com.squareup.moshi.JsonClass class * 33 | -keep class <1>JsonAdapter { 34 | (...); 35 | ; 36 | } 37 | -if @com.squareup.moshi.JsonClass class **$* 38 | -keep class <1>_<2>JsonAdapter { 39 | (...); 40 | ; 41 | } 42 | -if @com.squareup.moshi.JsonClass class **$*$* 43 | -keep class <1>_<2>_<3>JsonAdapter { 44 | (...); 45 | ; 46 | } 47 | -if @com.squareup.moshi.JsonClass class **$*$*$* 48 | -keep class <1>_<2>_<3>_<4>JsonAdapter { 49 | (...); 50 | ; 51 | } 52 | -if @com.squareup.moshi.JsonClass class **$*$*$*$* 53 | -keep class <1>_<2>_<3>_<4>_<5>JsonAdapter { 54 | (...); 55 | ; 56 | } 57 | -if @com.squareup.moshi.JsonClass class **$*$*$*$*$* 58 | -keep class <1>_<2>_<3>_<4>_<5>_<6>JsonAdapter { 59 | (...); 60 | ; 61 | } 62 | -------------------------------------------------------------------------------- /moshi/src/test/java/android/util/Pair.java: -------------------------------------------------------------------------------- 1 | package android.util; 2 | 3 | public final class Pair { 4 | } 5 | -------------------------------------------------------------------------------- /moshi/src/test/java/com/squareup/moshi/CircularAdaptersTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import com.squareup.moshi.internal.Util; 19 | import java.io.IOException; 20 | import java.lang.annotation.Annotation; 21 | import java.lang.annotation.Retention; 22 | import java.lang.reflect.Type; 23 | import java.util.Set; 24 | import org.junit.Test; 25 | 26 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | 29 | public final class CircularAdaptersTest { 30 | static class Team { 31 | final String lead; 32 | final Project[] projects; 33 | 34 | public Team(String lead, Project... projects) { 35 | this.lead = lead; 36 | this.projects = projects; 37 | } 38 | } 39 | 40 | static class Project { 41 | final String name; 42 | final Team[] teams; 43 | 44 | Project(String name, Team... teams) { 45 | this.name = name; 46 | this.teams = teams; 47 | } 48 | } 49 | 50 | @Test public void circularAdapters() throws Exception { 51 | Moshi moshi = new Moshi.Builder().build(); 52 | JsonAdapter teamAdapter = moshi.adapter(Team.class); 53 | 54 | Team team = new Team("Alice", new Project("King", new Team("Charlie", 55 | new Project("Delivery", null)))); 56 | assertThat(teamAdapter.toJson(team)).isEqualTo("{\"lead\":\"Alice\",\"projects\":[{\"name\":" 57 | + "\"King\",\"teams\":[{\"lead\":\"Charlie\",\"projects\":[{\"name\":\"Delivery\"}]}]}]}"); 58 | 59 | Team fromJson = teamAdapter.fromJson("{\"lead\":\"Alice\",\"projects\":[{\"name\":" 60 | + "\"King\",\"teams\":[{\"lead\":\"Charlie\",\"projects\":[{\"name\":\"Delivery\"}]}]}]}"); 61 | assertThat(fromJson.lead).isEqualTo("Alice"); 62 | assertThat(fromJson.projects[0].name).isEqualTo("King"); 63 | assertThat(fromJson.projects[0].teams[0].lead).isEqualTo("Charlie"); 64 | assertThat(fromJson.projects[0].teams[0].projects[0].name).isEqualTo("Delivery"); 65 | } 66 | 67 | @Retention(RUNTIME) 68 | @JsonQualifier 69 | public @interface Left { 70 | } 71 | 72 | @Retention(RUNTIME) 73 | @JsonQualifier 74 | public @interface Right { 75 | } 76 | 77 | static class Node { 78 | final String name; 79 | final @Left Node left; 80 | final @Right Node right; 81 | 82 | Node(String name, Node left, Node right) { 83 | this.name = name; 84 | this.left = left; 85 | this.right = right; 86 | } 87 | 88 | Node plusPrefix(String prefix) { 89 | return new Node(prefix + name, left, right); 90 | } 91 | 92 | Node minusPrefix(String prefix) { 93 | if (!name.startsWith(prefix)) throw new IllegalArgumentException(); 94 | return new Node(name.substring(prefix.length()), left, right); 95 | } 96 | } 97 | 98 | /** 99 | * This factory uses extensive delegation. Each node delegates to this for the left and right 100 | * subtrees, and those delegate to the built-in class adapter to do most of the serialization 101 | * work. 102 | */ 103 | static class PrefixingNodeFactory implements JsonAdapter.Factory { 104 | @Override public JsonAdapter create( 105 | Type type, Set annotations, Moshi moshi) { 106 | if (type != Node.class) return null; 107 | 108 | final String prefix; 109 | if (Util.isAnnotationPresent(annotations, Left.class)) { 110 | prefix = "L "; 111 | } else if (Util.isAnnotationPresent(annotations, Right.class)) { 112 | prefix = "R "; 113 | } else { 114 | return null; 115 | } 116 | 117 | final JsonAdapter delegate = moshi.nextAdapter(this, Node.class, Util.NO_ANNOTATIONS); 118 | 119 | return new JsonAdapter() { 120 | @Override public void toJson(JsonWriter writer, Node value) throws IOException { 121 | delegate.toJson(writer, value.plusPrefix(prefix)); 122 | } 123 | 124 | @Override public Node fromJson(JsonReader reader) throws IOException { 125 | Node result = delegate.fromJson(reader); 126 | return result.minusPrefix(prefix); 127 | } 128 | }.nullSafe(); 129 | } 130 | } 131 | 132 | @Test public void circularAdaptersAndAnnotations() throws Exception { 133 | Moshi moshi = new Moshi.Builder() 134 | .add(new PrefixingNodeFactory()) 135 | .build(); 136 | JsonAdapter nodeAdapter = moshi.adapter(Node.class); 137 | 138 | Node tree = new Node("C", 139 | new Node("A", null, new Node("B", null, null)), 140 | new Node("D", null, new Node("E", null, null))); 141 | assertThat(nodeAdapter.toJson(tree)).isEqualTo("{" 142 | + "\"left\":{\"name\":\"L A\",\"right\":{\"name\":\"R B\"}}," 143 | + "\"name\":\"C\"," 144 | + "\"right\":{\"name\":\"R D\",\"right\":{\"name\":\"R E\"}}" 145 | + "}"); 146 | 147 | Node fromJson = nodeAdapter.fromJson("{" 148 | + "\"left\":{\"name\":\"L A\",\"right\":{\"name\":\"R B\"}}," 149 | + "\"name\":\"C\"," 150 | + "\"right\":{\"name\":\"R D\",\"right\":{\"name\":\"R E\"}}" 151 | + "}"); 152 | assertThat(fromJson.name).isEqualTo("C"); 153 | assertThat(fromJson.left.name).isEqualTo("A"); 154 | assertThat(fromJson.left.right.name).isEqualTo("B"); 155 | assertThat(fromJson.right.name).isEqualTo("D"); 156 | assertThat(fromJson.right.right.name).isEqualTo("E"); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /moshi/src/test/java/com/squareup/moshi/DeferredAdapterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.lang.reflect.Type; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Set; 23 | import java.util.concurrent.ExecutionException; 24 | import java.util.concurrent.ExecutorService; 25 | import java.util.concurrent.Executors; 26 | import java.util.concurrent.Future; 27 | import javax.annotation.Nullable; 28 | import org.junit.Test; 29 | 30 | import static org.assertj.core.api.Assertions.assertThat; 31 | 32 | public final class DeferredAdapterTest { 33 | /** 34 | * When a type's JsonAdapter is circularly-dependent, Moshi creates a 'deferred adapter' to make 35 | * the cycle work. It's important that any adapters that depend on this deferred adapter don't 36 | * leak out until it's ready. 37 | * 38 | *

This test sets up a circular dependency [BlueNode -> GreenNode -> BlueNode] and then tries 39 | * to use a GreenNode JSON adapter before the BlueNode JSON adapter is built. It creates a 40 | * similar cycle [BlueNode -> RedNode -> BlueNode] so the order adapters are retrieved is 41 | * insignificant. 42 | * 43 | *

This used to trigger a crash because we'd incorrectly put the GreenNode JSON adapter in the 44 | * cache even though it depended upon an incomplete BlueNode JSON adapter. 45 | */ 46 | @Test public void concurrentSafe() { 47 | final List failures = new ArrayList<>(); 48 | 49 | JsonAdapter.Factory factory = new JsonAdapter.Factory() { 50 | int redAndGreenCount = 0; 51 | 52 | @Override public @Nullable JsonAdapter create( 53 | Type type, Set annotations, final Moshi moshi) { 54 | if ((type == RedNode.class || type == GreenNode.class) && redAndGreenCount++ == 1) { 55 | doInAnotherThread(new Runnable() { 56 | @Override public void run() { 57 | GreenNode greenBlue = new GreenNode(new BlueNode(null, null)); 58 | assertThat(moshi.adapter(GreenNode.class).toJson(greenBlue)) 59 | .isEqualTo("{\"blue\":{}}"); 60 | 61 | RedNode redBlue = new RedNode(new BlueNode(null, null)); 62 | assertThat(moshi.adapter(RedNode.class).toJson(redBlue)) 63 | .isEqualTo("{\"blue\":{}}"); 64 | } 65 | }); 66 | } 67 | return null; 68 | } 69 | }; 70 | 71 | Moshi moshi = new Moshi.Builder() 72 | .add(factory) 73 | .build(); 74 | 75 | JsonAdapter jsonAdapter = moshi.adapter(BlueNode.class); 76 | assertThat(jsonAdapter.toJson(new BlueNode(new GreenNode(new BlueNode(null, null)), null))) 77 | .isEqualTo("{\"green\":{\"blue\":{}}}"); 78 | 79 | assertThat(failures).isEmpty(); 80 | } 81 | 82 | private void doInAnotherThread(Runnable runnable) { 83 | ExecutorService executor = Executors.newSingleThreadExecutor(); 84 | Future future = executor.submit(runnable); 85 | executor.shutdown(); 86 | try { 87 | future.get(); 88 | } catch (InterruptedException e) { 89 | throw new RuntimeException(e); 90 | } catch (ExecutionException e) { 91 | throw new RuntimeException(e.getCause()); 92 | } 93 | } 94 | 95 | static class BlueNode { 96 | @Nullable GreenNode green; 97 | @Nullable RedNode red; 98 | 99 | BlueNode(@Nullable GreenNode green, @Nullable RedNode red) { 100 | this.green = green; 101 | this.red = red; 102 | } 103 | } 104 | 105 | static class RedNode { 106 | @Nullable BlueNode blue; 107 | 108 | RedNode(@Nullable BlueNode blue) { 109 | this.blue = blue; 110 | } 111 | } 112 | 113 | static class GreenNode { 114 | @Nullable BlueNode blue; 115 | 116 | GreenNode(@Nullable BlueNode blue) { 117 | this.blue = blue; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /moshi/src/test/java/com/squareup/moshi/JsonCodecFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.io.IOException; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import okio.Buffer; 22 | 23 | abstract class JsonCodecFactory { 24 | private static final Moshi MOSHI = new Moshi.Builder().build(); 25 | private static final JsonAdapter OBJECT_ADAPTER = MOSHI.adapter(Object.class); 26 | 27 | static List factories() { 28 | final JsonCodecFactory utf8 = new JsonCodecFactory() { 29 | Buffer buffer; 30 | 31 | @Override public JsonReader newReader(String json) { 32 | Buffer buffer = new Buffer().writeUtf8(json); 33 | return JsonReader.of(buffer); 34 | } 35 | 36 | @Override JsonWriter newWriter() { 37 | buffer = new Buffer(); 38 | return new JsonUtf8Writer(buffer); 39 | } 40 | 41 | @Override String json() { 42 | String result = buffer.readUtf8(); 43 | buffer = null; 44 | return result; 45 | } 46 | 47 | @Override boolean encodesToBytes() { 48 | return true; 49 | } 50 | 51 | @Override public String toString() { 52 | return "Utf8"; 53 | } 54 | }; 55 | 56 | final JsonCodecFactory value = new JsonCodecFactory() { 57 | JsonValueWriter writer; 58 | 59 | @Override public JsonReader newReader(String json) throws IOException { 60 | Moshi moshi = new Moshi.Builder().build(); 61 | Object object = moshi.adapter(Object.class).lenient().fromJson(json); 62 | return new JsonValueReader(object); 63 | } 64 | 65 | // TODO(jwilson): fix precision checks and delete his method. 66 | @Override boolean implementsStrictPrecision() { 67 | return false; 68 | } 69 | 70 | @Override JsonWriter newWriter() { 71 | writer = new JsonValueWriter(); 72 | return writer; 73 | } 74 | 75 | @Override String json() { 76 | // This writer writes a DOM. Use other Moshi features to serialize it as a string. 77 | try { 78 | Buffer buffer = new Buffer(); 79 | JsonWriter bufferedSinkWriter = JsonWriter.of(buffer); 80 | bufferedSinkWriter.setSerializeNulls(true); 81 | bufferedSinkWriter.setLenient(true); 82 | OBJECT_ADAPTER.toJson(bufferedSinkWriter, writer.root()); 83 | return buffer.readUtf8(); 84 | } catch (IOException e) { 85 | throw new AssertionError(); 86 | } 87 | } 88 | 89 | // TODO(jwilson): support BigDecimal and BigInteger and delete his method. 90 | @Override boolean supportsBigNumbers() { 91 | return false; 92 | } 93 | 94 | @Override public String toString() { 95 | return "Value"; 96 | } 97 | }; 98 | 99 | final JsonCodecFactory valuePeek = new JsonCodecFactory() { 100 | @Override public JsonReader newReader(String json) throws IOException { 101 | return value.newReader(json).peekJson(); 102 | } 103 | 104 | // TODO(jwilson): fix precision checks and delete his method. 105 | @Override boolean implementsStrictPrecision() { 106 | return false; 107 | } 108 | 109 | @Override JsonWriter newWriter() { 110 | return value.newWriter(); 111 | } 112 | 113 | @Override String json() { 114 | return value.json(); 115 | } 116 | 117 | // TODO(jwilson): support BigDecimal and BigInteger and delete his method. 118 | @Override boolean supportsBigNumbers() { 119 | return false; 120 | } 121 | 122 | @Override public String toString() { 123 | return "ValuePeek"; 124 | } 125 | }; 126 | 127 | return Arrays.asList( 128 | new Object[] { utf8 }, 129 | new Object[] { value }, 130 | new Object[] { valuePeek }); 131 | } 132 | 133 | abstract JsonReader newReader(String json) throws IOException; 134 | 135 | abstract JsonWriter newWriter(); 136 | 137 | boolean implementsStrictPrecision() { 138 | return true; 139 | } 140 | 141 | abstract String json(); 142 | 143 | boolean encodesToBytes() { 144 | return false; 145 | } 146 | 147 | boolean supportsBigNumbers() { 148 | return true; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /moshi/src/test/java/com/squareup/moshi/JsonReaderPathTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.io.IOException; 19 | import java.util.List; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.Parameterized; 23 | import org.junit.runners.Parameterized.Parameter; 24 | import org.junit.runners.Parameterized.Parameters; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | import static org.junit.Assume.assumeTrue; 28 | 29 | @RunWith(Parameterized.class) 30 | public final class JsonReaderPathTest { 31 | @Parameter public JsonCodecFactory factory; 32 | 33 | @Parameters(name = "{0}") 34 | public static List parameters() { 35 | return JsonCodecFactory.factories(); 36 | } 37 | 38 | @SuppressWarnings("CheckReturnValue") 39 | @Test public void path() throws IOException { 40 | JsonReader reader = factory.newReader("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}"); 41 | assertThat(reader.getPath()).isEqualTo("$"); 42 | reader.beginObject(); 43 | assertThat(reader.getPath()).isEqualTo("$."); 44 | reader.nextName(); 45 | assertThat(reader.getPath()).isEqualTo("$.a"); 46 | reader.beginArray(); 47 | assertThat(reader.getPath()).isEqualTo("$.a[0]"); 48 | reader.nextInt(); 49 | assertThat(reader.getPath()).isEqualTo("$.a[1]"); 50 | reader.nextBoolean(); 51 | assertThat(reader.getPath()).isEqualTo("$.a[2]"); 52 | reader.nextBoolean(); 53 | assertThat(reader.getPath()).isEqualTo("$.a[3]"); 54 | reader.nextNull(); 55 | assertThat(reader.getPath()).isEqualTo("$.a[4]"); 56 | reader.nextString(); 57 | assertThat(reader.getPath()).isEqualTo("$.a[5]"); 58 | reader.beginObject(); 59 | assertThat(reader.getPath()).isEqualTo("$.a[5]."); 60 | reader.nextName(); 61 | assertThat(reader.getPath()).isEqualTo("$.a[5].c"); 62 | reader.nextString(); 63 | assertThat(reader.getPath()).isEqualTo("$.a[5].c"); 64 | reader.endObject(); 65 | assertThat(reader.getPath()).isEqualTo("$.a[6]"); 66 | reader.beginArray(); 67 | assertThat(reader.getPath()).isEqualTo("$.a[6][0]"); 68 | reader.nextInt(); 69 | assertThat(reader.getPath()).isEqualTo("$.a[6][1]"); 70 | reader.endArray(); 71 | assertThat(reader.getPath()).isEqualTo("$.a[7]"); 72 | reader.endArray(); 73 | assertThat(reader.getPath()).isEqualTo("$.a"); 74 | reader.endObject(); 75 | assertThat(reader.getPath()).isEqualTo("$"); 76 | } 77 | 78 | @Test public void arrayOfObjects() throws IOException { 79 | JsonReader reader = factory.newReader("[{},{},{}]"); 80 | reader.beginArray(); 81 | assertThat(reader.getPath()).isEqualTo("$[0]"); 82 | reader.beginObject(); 83 | assertThat(reader.getPath()).isEqualTo("$[0]."); 84 | reader.endObject(); 85 | assertThat(reader.getPath()).isEqualTo("$[1]"); 86 | reader.beginObject(); 87 | assertThat(reader.getPath()).isEqualTo("$[1]."); 88 | reader.endObject(); 89 | assertThat(reader.getPath()).isEqualTo("$[2]"); 90 | reader.beginObject(); 91 | assertThat(reader.getPath()).isEqualTo("$[2]."); 92 | reader.endObject(); 93 | assertThat(reader.getPath()).isEqualTo("$[3]"); 94 | reader.endArray(); 95 | assertThat(reader.getPath()).isEqualTo("$"); 96 | } 97 | 98 | @Test public void arrayOfArrays() throws IOException { 99 | JsonReader reader = factory.newReader("[[],[],[]]"); 100 | reader.beginArray(); 101 | assertThat(reader.getPath()).isEqualTo("$[0]"); 102 | reader.beginArray(); 103 | assertThat(reader.getPath()).isEqualTo("$[0][0]"); 104 | reader.endArray(); 105 | assertThat(reader.getPath()).isEqualTo("$[1]"); 106 | reader.beginArray(); 107 | assertThat(reader.getPath()).isEqualTo("$[1][0]"); 108 | reader.endArray(); 109 | assertThat(reader.getPath()).isEqualTo("$[2]"); 110 | reader.beginArray(); 111 | assertThat(reader.getPath()).isEqualTo("$[2][0]"); 112 | reader.endArray(); 113 | assertThat(reader.getPath()).isEqualTo("$[3]"); 114 | reader.endArray(); 115 | assertThat(reader.getPath()).isEqualTo("$"); 116 | } 117 | 118 | @SuppressWarnings("CheckReturnValue") 119 | @Test public void objectPath() throws IOException { 120 | JsonReader reader = factory.newReader("{\"a\":1,\"b\":2}"); 121 | assertThat(reader.getPath()).isEqualTo("$"); 122 | 123 | reader.peek(); 124 | assertThat(reader.getPath()).isEqualTo("$"); 125 | reader.beginObject(); 126 | assertThat(reader.getPath()).isEqualTo("$."); 127 | 128 | reader.peek(); 129 | assertThat(reader.getPath()).isEqualTo("$."); 130 | reader.nextName(); 131 | assertThat(reader.getPath()).isEqualTo("$.a"); 132 | 133 | reader.peek(); 134 | assertThat(reader.getPath()).isEqualTo("$.a"); 135 | reader.nextInt(); 136 | assertThat(reader.getPath()).isEqualTo("$.a"); 137 | 138 | reader.peek(); 139 | assertThat(reader.getPath()).isEqualTo("$.a"); 140 | reader.nextName(); 141 | assertThat(reader.getPath()).isEqualTo("$.b"); 142 | 143 | reader.peek(); 144 | assertThat(reader.getPath()).isEqualTo("$.b"); 145 | reader.nextInt(); 146 | assertThat(reader.getPath()).isEqualTo("$.b"); 147 | 148 | reader.peek(); 149 | assertThat(reader.getPath()).isEqualTo("$.b"); 150 | reader.endObject(); 151 | assertThat(reader.getPath()).isEqualTo("$"); 152 | 153 | reader.peek(); 154 | assertThat(reader.getPath()).isEqualTo("$"); 155 | reader.close(); 156 | assertThat(reader.getPath()).isEqualTo("$"); 157 | } 158 | 159 | @SuppressWarnings("CheckReturnValue") 160 | @Test public void arrayPath() throws IOException { 161 | JsonReader reader = factory.newReader("[1,2]"); 162 | assertThat(reader.getPath()).isEqualTo("$"); 163 | 164 | reader.peek(); 165 | assertThat(reader.getPath()).isEqualTo("$"); 166 | reader.beginArray(); 167 | assertThat(reader.getPath()).isEqualTo("$[0]"); 168 | 169 | reader.peek(); 170 | assertThat(reader.getPath()).isEqualTo("$[0]"); 171 | reader.nextInt(); 172 | assertThat(reader.getPath()).isEqualTo("$[1]"); 173 | 174 | reader.peek(); 175 | assertThat(reader.getPath()).isEqualTo("$[1]"); 176 | reader.nextInt(); 177 | assertThat(reader.getPath()).isEqualTo("$[2]"); 178 | 179 | reader.peek(); 180 | assertThat(reader.getPath()).isEqualTo("$[2]"); 181 | reader.endArray(); 182 | assertThat(reader.getPath()).isEqualTo("$"); 183 | 184 | reader.peek(); 185 | assertThat(reader.getPath()).isEqualTo("$"); 186 | reader.close(); 187 | assertThat(reader.getPath()).isEqualTo("$"); 188 | } 189 | 190 | @Test public void multipleTopLevelValuesInOneDocument() throws IOException { 191 | assumeTrue(factory.encodesToBytes()); 192 | 193 | JsonReader reader = factory.newReader("[][]"); 194 | reader.setLenient(true); 195 | reader.beginArray(); 196 | reader.endArray(); 197 | assertThat(reader.getPath()).isEqualTo("$"); 198 | reader.beginArray(); 199 | reader.endArray(); 200 | assertThat(reader.getPath()).isEqualTo("$"); 201 | } 202 | 203 | @Test public void skipArrayElements() throws IOException { 204 | JsonReader reader = factory.newReader("[1,2,3]"); 205 | reader.beginArray(); 206 | reader.skipValue(); 207 | reader.skipValue(); 208 | assertThat(reader.getPath()).isEqualTo("$[2]"); 209 | } 210 | 211 | @Test public void skipObjectNames() throws IOException { 212 | JsonReader reader = factory.newReader("{\"a\":1}"); 213 | reader.beginObject(); 214 | reader.skipValue(); 215 | assertThat(reader.getPath()).isEqualTo("$.null"); 216 | } 217 | 218 | @SuppressWarnings("CheckReturnValue") 219 | @Test public void skipObjectValues() throws IOException { 220 | JsonReader reader = factory.newReader("{\"a\":1,\"b\":2}"); 221 | reader.beginObject(); 222 | reader.nextName(); 223 | reader.skipValue(); 224 | assertThat(reader.getPath()).isEqualTo("$.null"); 225 | reader.nextName(); 226 | assertThat(reader.getPath()).isEqualTo("$.b"); 227 | } 228 | 229 | @Test public void skipNestedStructures() throws IOException { 230 | JsonReader reader = factory.newReader("[[1,2,3],4]"); 231 | reader.beginArray(); 232 | reader.skipValue(); 233 | assertThat(reader.getPath()).isEqualTo("$[1]"); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /moshi/src/test/java/com/squareup/moshi/JsonUtf8WriterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.io.IOException; 19 | import okio.Buffer; 20 | import org.junit.Test; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | 24 | public final class JsonUtf8WriterTest { 25 | @Test public void prettyPrintObject() throws IOException { 26 | Buffer buffer = new Buffer(); 27 | JsonWriter writer = JsonWriter.of(buffer); 28 | writer.setSerializeNulls(true); 29 | writer.setIndent(" "); 30 | 31 | writer.beginObject(); 32 | writer.name("a").value(true); 33 | writer.name("b").value(false); 34 | writer.name("c").value(5.0); 35 | writer.name("e").nullValue(); 36 | writer.name("f").beginArray(); 37 | writer.value(6.0); 38 | writer.value(7.0); 39 | writer.endArray(); 40 | writer.name("g").beginObject(); 41 | writer.name("h").value(8.0); 42 | writer.name("i").value(9.0); 43 | writer.endObject(); 44 | writer.endObject(); 45 | 46 | String expected = "{\n" 47 | + " \"a\": true,\n" 48 | + " \"b\": false,\n" 49 | + " \"c\": 5.0,\n" 50 | + " \"e\": null,\n" 51 | + " \"f\": [\n" 52 | + " 6.0,\n" 53 | + " 7.0\n" 54 | + " ],\n" 55 | + " \"g\": {\n" 56 | + " \"h\": 8.0,\n" 57 | + " \"i\": 9.0\n" 58 | + " }\n" 59 | + "}"; 60 | assertThat(buffer.readUtf8()).isEqualTo(expected); 61 | } 62 | 63 | @Test public void prettyPrintArray() throws IOException { 64 | Buffer buffer = new Buffer(); 65 | JsonWriter writer = JsonWriter.of(buffer); 66 | writer.setIndent(" "); 67 | 68 | writer.beginArray(); 69 | writer.value(true); 70 | writer.value(false); 71 | writer.value(5.0); 72 | writer.nullValue(); 73 | writer.beginObject(); 74 | writer.name("a").value(6.0); 75 | writer.name("b").value(7.0); 76 | writer.endObject(); 77 | writer.beginArray(); 78 | writer.value(8.0); 79 | writer.value(9.0); 80 | writer.endArray(); 81 | writer.endArray(); 82 | 83 | String expected = "[\n" 84 | + " true,\n" 85 | + " false,\n" 86 | + " 5.0,\n" 87 | + " null,\n" 88 | + " {\n" 89 | + " \"a\": 6.0,\n" 90 | + " \"b\": 7.0\n" 91 | + " },\n" 92 | + " [\n" 93 | + " 8.0,\n" 94 | + " 9.0\n" 95 | + " ]\n" 96 | + "]"; 97 | assertThat(buffer.readUtf8()).isEqualTo(expected); 98 | } 99 | 100 | @Test public void repeatedNameIgnored() throws IOException { 101 | Buffer buffer = new Buffer(); 102 | JsonWriter writer = JsonWriter.of(buffer); 103 | writer.beginObject(); 104 | writer.name("a").value(1); 105 | writer.name("a").value(2); 106 | writer.endObject(); 107 | // JsonWriter doesn't attempt to detect duplicate names 108 | assertThat(buffer.readUtf8()).isEqualTo("{\"a\":1,\"a\":2}"); 109 | } 110 | 111 | @Test public void valueFromSource() throws IOException { 112 | Buffer buffer = new Buffer(); 113 | JsonWriter writer = JsonUtf8Writer.of(buffer); 114 | writer.beginObject(); 115 | writer.name("a"); 116 | writer.value(new Buffer().writeUtf8("[\"value\"]")); 117 | writer.name("b"); 118 | writer.value(new Buffer().writeUtf8("2")); 119 | writer.name("c"); 120 | writer.value(3); 121 | writer.name("d"); 122 | writer.value(new Buffer().writeUtf8("null")); 123 | writer.endObject(); 124 | assertThat(buffer.readUtf8()).isEqualTo("{\"a\":[\"value\"],\"b\":2,\"c\":3,\"d\":null}"); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /moshi/src/test/java/com/squareup/moshi/RecursiveTypesResolveTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Gson Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.squareup.moshi; 18 | 19 | import com.squareup.moshi.internal.Util; 20 | import org.junit.Test; 21 | 22 | import static org.junit.Assert.assertEquals; 23 | import static org.junit.Assert.assertNotNull; 24 | 25 | /** 26 | * Test fixes for infinite recursion on {@link Util#resolve(java.lang.reflect.Type, Class, 27 | * java.lang.reflect.Type)}, described at Issue #440 28 | * and similar issues. 29 | *

30 | * These tests originally caused {@link StackOverflowError} because of infinite recursion on attempts to 31 | * resolve generics on types, with an intermediate types like 'Foo2<? extends ? super ? extends ... ? extends A>' 32 | *

33 | * Adapted from https://github.com/google/gson/commit/a300148003e3a067875b1444e8268b6e0f0e0e02 in 34 | * service of https://github.com/square/moshi/issues/338. 35 | */ 36 | public final class RecursiveTypesResolveTest { 37 | 38 | private static class Foo1 { 39 | public Foo2 foo2; 40 | } 41 | 42 | private static class Foo2 { 43 | public Foo1 foo1; 44 | } 45 | 46 | /** 47 | * Test simplest case of recursion. 48 | */ 49 | @Test public void recursiveResolveSimple() { 50 | JsonAdapter adapter = new Moshi.Builder().build().adapter(Foo1.class); 51 | assertNotNull(adapter); 52 | } 53 | 54 | // 55 | // Tests belows check the behaviour of the methods changed for the fix 56 | // 57 | 58 | @Test public void doubleSupertype() { 59 | assertEquals(Types.supertypeOf(Number.class), 60 | Types.supertypeOf(Types.supertypeOf(Number.class))); 61 | } 62 | 63 | @Test public void doubleSubtype() { 64 | assertEquals(Types.subtypeOf(Number.class), 65 | Types.subtypeOf(Types.subtypeOf(Number.class))); 66 | } 67 | 68 | @Test public void superSubtype() { 69 | assertEquals(Types.subtypeOf(Object.class), 70 | Types.supertypeOf(Types.subtypeOf(Number.class))); 71 | } 72 | 73 | @Test public void subSupertype() { 74 | assertEquals(Types.subtypeOf(Object.class), 75 | Types.subtypeOf(Types.supertypeOf(Number.class))); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /moshi/src/test/java/com/squareup/moshi/TestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.moshi; 17 | 18 | import java.util.Arrays; 19 | import okio.Buffer; 20 | 21 | final class TestUtil { 22 | static final int MAX_DEPTH = 255; 23 | 24 | static JsonReader newReader(String json) { 25 | Buffer buffer = new Buffer().writeUtf8(json); 26 | return JsonReader.of(buffer); 27 | } 28 | 29 | static String repeat(char c, int count) { 30 | char[] array = new char[count]; 31 | Arrays.fill(array, c); 32 | return new String(array); 33 | } 34 | 35 | static String repeat(String s, int count) { 36 | StringBuilder result = new StringBuilder(s.length() * count); 37 | for (int i = 0; i < count; i++) { 38 | result.append(s); 39 | } 40 | return result.toString(); 41 | } 42 | 43 | private TestUtil() { 44 | throw new AssertionError("No instances."); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | org.sonatype.oss 8 | oss-parent 9 | 7 10 | 11 | 12 | com.squareup.moshi 13 | moshi-parent 14 | 1.10.0-SNAPSHOT 15 | pom 16 | Moshi (Parent) 17 | A modern JSON API for Android and Java 18 | https://github.com/square/moshi 19 | 20 | 21 | moshi 22 | examples 23 | adapters 24 | kotlin/codegen 25 | kotlin/reflect 26 | kotlin/tests 27 | 28 | 29 | 30 | UTF-8 31 | 1.7 32 | 33 | 34 | 1.0-rc5 35 | 0.9.17 36 | 0.2 37 | 1.16.0 38 | 2.1.0 39 | 1.3.50 40 | 1.4.4 41 | 0.1.0 42 | 3.1.0 43 | 44 | 45 | 3.11.1 46 | 0.15 47 | 4.12 48 | 1.2.3 49 | 1.0 50 | 51 | 52 | 53 | https://github.com/square/moshi/ 54 | scm:git:https://github.com/square/moshi.git 55 | scm:git:git@github.com:square/moshi.git 56 | HEAD 57 | 58 | 59 | 60 | GitHub Issues 61 | https://github.com/square/moshi/issues 62 | 63 | 64 | 65 | 66 | Apache 2.0 67 | http://www.apache.org/licenses/LICENSE-2.0.txt 68 | 69 | 70 | 71 | 72 | 73 | 74 | com.google.code.findbugs 75 | jsr305 76 | 3.0.2 77 | provided 78 | 79 | 80 | junit 81 | junit 82 | ${junit.version} 83 | 84 | 85 | com.squareup.okio 86 | okio 87 | ${okio.version} 88 | 89 | 90 | org.assertj 91 | assertj-core 92 | ${assertj.version} 93 | 94 | 95 | org.jetbrains.kotlin 96 | kotlin-stdlib 97 | ${kotlin.version} 98 | 99 | 100 | org.jetbrains.kotlin 101 | kotlin-reflect 102 | ${kotlin.version} 103 | 104 | 105 | org.jetbrains.kotlin 106 | kotlin-test 107 | ${kotlin.version} 108 | test 109 | 110 | 111 | org.jetbrains.kotlin 112 | kotlin-compiler-embeddable 113 | ${kotlin.version} 114 | 115 | 116 | org.jetbrains.kotlin 117 | kotlin-annotation-processing-embeddable 118 | ${kotlin.version} 119 | 120 | 121 | com.google.testing.compile 122 | compile-testing 123 | ${compile-testing.version} 124 | 125 | 126 | 127 | 128 | 129 | 130 | jcenter 131 | JCenter 132 | https://jcenter.bintray.com/ 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-compiler-plugin 142 | 3.8.1 143 | 144 | javac-with-errorprone 145 | true 146 | ${java.version} 147 | ${java.version} 148 | 149 | 150 | 151 | org.codehaus.plexus 152 | plexus-compiler-javac-errorprone 153 | 2.8 154 | 155 | 156 | com.google.errorprone 157 | error_prone_core 158 | 2.3.1 159 | 160 | 161 | 162 | 163 | org.jetbrains.kotlin 164 | kotlin-maven-plugin 165 | ${kotlin.version} 166 | 167 | 168 | 169 | 170 | 171 | 172 | org.apache.maven.plugins 173 | maven-checkstyle-plugin 174 | 2.17 175 | 176 | 177 | com.puppycrawl.tools 178 | checkstyle 179 | 7.7 180 | 181 | 182 | 183 | true 184 | checkstyle.xml 185 | true 186 | **/Iso8601Utils.java 187 | 188 | 189 | 190 | verify 191 | 192 | checkstyle 193 | 194 | 195 | 196 | 197 | 198 | org.apache.maven.plugins 199 | maven-release-plugin 200 | 2.5 201 | 202 | true 203 | 204 | 205 | 206 | 207 | 208 | --------------------------------------------------------------------------------