├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ └── raw │ │ │ │ └── data.json │ │ ├── java │ │ │ └── br │ │ │ │ └── com │ │ │ │ └── gustavofao │ │ │ │ └── jsonapisample │ │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml │ └── test │ │ └── java │ │ └── br │ │ └── com │ │ └── gustavofao │ │ └── jsonapisample │ │ └── ExampleUnitTest.java ├── proguard-rules.pro └── build.gradle ├── JsonAPI ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── gustavofao │ │ │ └── jsonapi │ │ │ ├── Annotations │ │ │ ├── Excluded.java │ │ │ ├── Id.java │ │ │ ├── Type.java │ │ │ ├── Types.java │ │ │ └── SerialName.java │ │ │ ├── Models │ │ │ ├── JSONList.java │ │ │ ├── ErrorSource.java │ │ │ ├── ErrorModel.java │ │ │ ├── Links.java │ │ │ ├── JSONApiObject.java │ │ │ └── Resource.java │ │ │ ├── Retrofit │ │ │ ├── JSONRequestBodyConverter.java │ │ │ ├── JSONResponseBodyConverter.java │ │ │ └── JSONConverterFactory.java │ │ │ └── JSONApiConverter.java │ └── test │ │ ├── java │ │ ├── android │ │ │ └── util │ │ │ │ └── Log.java │ │ └── com │ │ │ └── gustavofao │ │ │ └── jsonapi │ │ │ ├── testmodels │ │ │ ├── City.java │ │ │ ├── InvalidResource.java │ │ │ ├── User.java │ │ │ ├── Conversation.java │ │ │ └── Person.java │ │ │ ├── testutils │ │ │ └── FileLoader.java │ │ │ └── JSONApiConverterTest.java │ │ └── resources │ │ └── jsons │ │ ├── person.json │ │ └── conversation.json ├── build.gradle └── proguard-rules.pro ├── settings.gradle ├── .idea ├── copyright │ └── profiles_settings.xml ├── markdown-navigator │ └── profiles_settings.xml ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── modules.xml ├── runConfigurations.xml └── compiler.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── JSONApi.iml ├── JSONApiSample.iml ├── .github └── workflows │ └── android.yml ├── publish.gradle ├── gradlew.bat ├── gradlew ├── README.md └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /JsonAPI/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':JsonAPI' 2 | -------------------------------------------------------------------------------- /JsonAPI/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | JSONApiSample 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faogustavo/JSONApi/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faogustavo/JSONApi/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faogustavo/JSONApi/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faogustavo/JSONApi/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faogustavo/JSONApi/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/faogustavo/JSONApi/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /JsonAPI/src/test/java/android/util/Log.java: -------------------------------------------------------------------------------- 1 | package android.util; 2 | 3 | public class Log { 4 | 5 | public static int e(String tag, String msg) { return 0; } 6 | 7 | } 8 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /JsonAPI/src/test/resources/jsons/person.json: -------------------------------------------------------------------------------- 1 | {"data":{"attributes":{"firstName":"Gustavo","city":"Santa Maria - RS, Brazil","name":"Gustavo","pseudo":"faogustavo","email":"faogustavo@gmail.com"},"id":"id-01","type":"person"}} -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Nov 26 19:12:00 BRST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Annotations/Excluded.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Annotations; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface Excluded { 8 | } 9 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Annotations/Id.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Id {} 11 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Annotations/Type.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Type { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Annotations/Types.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Types { 11 | String[] value(); 12 | } 13 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Annotations/SerialName.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface SerialName { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/test/java/br/com/gustavofao/jsonapisample/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package br.com.gustavofao.jsonapisample; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/br/com/gustavofao/jsonapisample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package br.com.gustavofao.jsonapisample; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | public class MainActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_main); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Models/JSONList.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Models; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class JSONList extends ArrayList { 6 | 7 | private Links links; 8 | 9 | public JSONList() {} 10 | 11 | public void setLinks(Links links) { 12 | this.links = links; 13 | } 14 | 15 | public Links getLinks() { 16 | return links; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /JsonAPI/src/test/java/com/gustavofao/jsonapi/testmodels/City.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.testmodels; 2 | 3 | import com.gustavofao.jsonapi.Annotations.Type; 4 | import com.gustavofao.jsonapi.Models.Resource; 5 | 6 | @Type("cities") 7 | public class City extends Resource { 8 | private String locality; 9 | 10 | public String getLocality() { 11 | return locality; 12 | } 13 | 14 | public void setLocality(String locality) { 15 | this.locality = locality; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /JsonAPI/src/test/java/com/gustavofao/jsonapi/testmodels/InvalidResource.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.testmodels; 2 | 3 | import com.gustavofao.jsonapi.Annotations.Type; 4 | import com.gustavofao.jsonapi.Models.Resource; 5 | 6 | public class InvalidResource extends Resource { 7 | 8 | private String invalidField; 9 | 10 | public String getInvalidField() { 11 | return invalidField; 12 | } 13 | 14 | public void setInvalidField(String invalidField) { 15 | this.invalidField = invalidField; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /JsonAPI/src/test/java/com/gustavofao/jsonapi/testmodels/User.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.testmodels; 2 | 3 | import com.gustavofao.jsonapi.Annotations.Type; 4 | import com.gustavofao.jsonapi.Models.Resource; 5 | 6 | @Type("consumers") 7 | public class User extends Resource { 8 | 9 | private String email; 10 | private City city; 11 | 12 | public String getEmail() { 13 | return email; 14 | } 15 | 16 | public void setEmail(String email) { 17 | this.email = email; 18 | } 19 | 20 | public City getCity() { 21 | return city; 22 | } 23 | 24 | public void setCity(City city) { 25 | this.city = city; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Models/ErrorSource.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Models; 2 | 3 | /** 4 | * Created by Gustavo Fão Valvassori on 21/04/2016. 5 | * Propósito: ${CURSOR} 6 | */ 7 | public class ErrorSource { 8 | 9 | private String pointer; 10 | private String parameter; 11 | 12 | public String getPointer() { 13 | return pointer; 14 | } 15 | 16 | public void setPointer(String pointer) { 17 | this.pointer = pointer; 18 | } 19 | 20 | public String getParameter() { 21 | return parameter; 22 | } 23 | 24 | public void setParameter(String parameter) { 25 | this.parameter = parameter; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\gusta\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Application files 2 | *.apk 3 | *.ap_ 4 | 5 | /.gradle 6 | /local.properties 7 | .DS_Store 8 | /build 9 | /app/build 10 | /app/config/detekt/reports 11 | *\~ 12 | 13 | # OS generated files 14 | Thumbs.db 15 | 16 | # IDEA/Android Studio ignores 17 | **/*.iml 18 | **/*.ipr 19 | **/*.iws 20 | **/*.editorconfig 21 | **/.idea/shelf 22 | **/.idea/workspace.xml 23 | **/.idea/tasks.xml 24 | **/.idea/datasources.xml 25 | **/.idea/dataSources.ids 26 | **/.idea/gradle.xml 27 | **/.idea/misc.xml 28 | **/.idea/modules.xml 29 | **/.idea/libraries 30 | **/.idea/dictionaries 31 | **/.idea/runConfigurations.xml 32 | **/.idea/caches 33 | **/.idea/assetWizardSettings.xml 34 | **/.idea/encodings.xml 35 | **/.idea/codeStyles/codeStyleConfig.xml 36 | **/.idea/markdown-navigator 37 | 38 | ### Android NDK ### 39 | .externalNativeBuild 40 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /JSONApi.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /JsonAPI/src/test/java/com/gustavofao/jsonapi/testmodels/Conversation.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.testmodels; 2 | 3 | import com.gustavofao.jsonapi.Annotations.Type; 4 | import com.gustavofao.jsonapi.Models.Resource; 5 | 6 | import java.util.Date; 7 | 8 | @Type("conversation") 9 | public class Conversation extends Resource { 10 | 11 | private String message; 12 | private Date date; 13 | private Person person; 14 | 15 | public String getMessage() { 16 | return message; 17 | } 18 | 19 | public void setMessage(String message) { 20 | this.message = message; 21 | } 22 | 23 | public Date getDate() { 24 | return date; 25 | } 26 | 27 | public void setDate(Date date) { 28 | this.date = date; 29 | } 30 | 31 | public Person getPerson() { 32 | return person; 33 | } 34 | 35 | public void setPerson(Person person) { 36 | this.person = person; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/res/raw/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":"4214653", 4 | "type":"conversation", 5 | "attributes":{ 6 | "message":"content", 7 | "date":"2016-06-15T15:16:12-0300" 8 | }, 9 | "relationships": { 10 | "person" : { 11 | "data":{ 12 | "id":"451", 13 | "type":"person" 14 | } 15 | } 16 | } 17 | }, 18 | "included": [ 19 | { 20 | "type": "person", 21 | "id": "451", 22 | "attributes": { 23 | "name": "Gustavo Fão Valvassori" 24 | } 25 | } 26 | ], 27 | "links": { 28 | "self": "http://example.com/articles?page[number]=3&page[size]=1", 29 | "first": "http://example.com/articles?page[number]=1&page[size]=1", 30 | "prev": "http://example.com/articles?page[number]=2&page[size]=1", 31 | "next": "http://example.com/articles?page[number]=4&page[size]=1", 32 | "last": "http://example.com/articles?page[number]=13&page[size]=1" 33 | } 34 | } -------------------------------------------------------------------------------- /JsonAPI/src/test/resources/jsons/conversation.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":"4214653", 4 | "type":"conversation", 5 | "attributes":{ 6 | "message":"content", 7 | "date":"2016-06-15T15:16:12-0300" 8 | }, 9 | "relationships": { 10 | "person" : { 11 | "data":{ 12 | "id":"451", 13 | "type":"person" 14 | } 15 | } 16 | } 17 | }, 18 | "included": [ 19 | { 20 | "type": "person", 21 | "id": "451", 22 | "attributes": { 23 | "name": "Gustavo Fão Valvassori" 24 | } 25 | } 26 | ], 27 | "links": { 28 | "self": "http://example.com/articles?page[number]=3&page[size]=1", 29 | "first": "http://example.com/articles?page[number]=1&page[size]=1", 30 | "prev": "http://example.com/articles?page[number]=2&page[size]=1", 31 | "next": "http://example.com/articles?page[number]=4&page[size]=1", 32 | "last": "http://example.com/articles?page[number]=13&page[size]=1" 33 | } 34 | } -------------------------------------------------------------------------------- /JSONApiSample.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /JsonAPI/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | ext { 4 | PUBLISH_GROUP_ID = 'com.gustavofao' 5 | PUBLISH_ARTIFACT_ID = 'JSONApi' 6 | PUBLISH_VERSION = '1.0.10.0' 7 | } 8 | 9 | android { 10 | compileSdkVersion 29 11 | buildToolsVersion "29.0.2" 12 | 13 | defaultConfig { 14 | minSdkVersion 15 15 | targetSdkVersion 29 16 | versionCode 8 17 | versionName "1.0.9.4" 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | lintOptions { 26 | disable 'InvalidPackage' 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation 'com.squareup.retrofit2:retrofit:2.5.0' 32 | 33 | testImplementation 'junit:junit:4.12' 34 | testImplementation 'org.json:json:20190722' 35 | } 36 | 37 | // ./gradlew clean build generateRelease 38 | // gradlew.bat clean build generateRelease 39 | apply from: "$rootDir/publish.gradle" 40 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Retrofit/JSONRequestBodyConverter.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Retrofit; 2 | 3 | import com.gustavofao.jsonapi.JSONApiConverter; 4 | import com.gustavofao.jsonapi.Models.Resource; 5 | 6 | import java.io.IOException; 7 | import java.util.List; 8 | 9 | import okhttp3.MediaType; 10 | import okhttp3.RequestBody; 11 | import retrofit2.Converter; 12 | 13 | public class JSONRequestBodyConverter implements Converter { 14 | private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); 15 | 16 | private JSONApiConverter converter; 17 | 18 | public JSONRequestBodyConverter(JSONApiConverter converter) { 19 | this.converter = converter; 20 | } 21 | 22 | @Override 23 | public RequestBody convert(T value) throws IOException { 24 | if (value instanceof Resource) 25 | return RequestBody.create(MEDIA_TYPE, converter.toJson((Resource) value)); 26 | return RequestBody.create(MEDIA_TYPE, converter.toJson((List) value)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Models/ErrorModel.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Models; 2 | 3 | /** 4 | * Created by Gustavo Fão Valvassori on 21/04/2016. 5 | * Propósito: ${CURSOR} 6 | */ 7 | public class ErrorModel { 8 | 9 | private String status; 10 | private String title; 11 | private String detail; 12 | private ErrorSource source; 13 | 14 | public String getStatus() { 15 | return status; 16 | } 17 | 18 | public void setStatus(String status) { 19 | this.status = status; 20 | } 21 | 22 | public String getTitle() { 23 | return title; 24 | } 25 | 26 | public void setTitle(String title) { 27 | this.title = title; 28 | } 29 | 30 | public String getDetail() { 31 | return detail; 32 | } 33 | 34 | public void setDetail(String detail) { 35 | this.detail = detail; 36 | } 37 | 38 | public ErrorSource getSource() { 39 | return source; 40 | } 41 | 42 | public void setSource(ErrorSource source) { 43 | this.source = source; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | buildToolsVersion "28.0.3" 6 | 7 | defaultConfig { 8 | applicationId "br.com.gustavofao.jsonapisample" 9 | minSdkVersion 15 10 | targetSdkVersion 28 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | lintOptions { 21 | abortOnError false 22 | } 23 | packagingOptions { 24 | exclude 'META-INF/LICENSE' 25 | exclude 'META-INF/NOTICE' 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(include: ['*.jar'], dir: 'libs') 31 | implementation 'com.android.support:appcompat-v7:28.0.0' 32 | implementation 'com.squareup.retrofit2:retrofit:2.5.0' 33 | implementation 'com.squareup.okhttp3:okhttp:3.12.1' 34 | implementation project(':JsonAPI') 35 | 36 | testImplementation 'junit:junit:4.12' 37 | } 38 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Retrofit/JSONResponseBodyConverter.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Retrofit; 2 | 3 | import com.gustavofao.jsonapi.JSONApiConverter; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | 8 | import okhttp3.ResponseBody; 9 | import retrofit2.Converter; 10 | 11 | public class JSONResponseBodyConverter implements Converter { 12 | 13 | private JSONApiConverter converter; 14 | 15 | public JSONResponseBodyConverter(JSONApiConverter converter) { 16 | this.converter = converter; 17 | } 18 | 19 | @Override 20 | public T convert(ResponseBody value) throws IOException { 21 | try { 22 | BufferedReader reader = new BufferedReader(value.charStream()); 23 | StringBuilder builder = new StringBuilder(); 24 | String line; 25 | 26 | while ((line = reader.readLine()) != null) { 27 | builder.append(line); 28 | } 29 | 30 | return (T) converter.fromJson(builder.toString()); 31 | } finally { 32 | value.charStream().close(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | validate: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | 11 | - name: Gradle Cache 12 | uses: actions/cache@v1 13 | with: 14 | path: ~/.gradle/caches/ 15 | key: ${{ runner.OS }}-gradle-cache 16 | 17 | - name: Gradle Wraper Cache 18 | uses: actions/cache@v1 19 | with: 20 | path: ~/.gradle/wrapper/ 21 | key: ${{ runner.OS }}-gradle-wraper-cache 22 | 23 | - name: Android Build Cache 24 | uses: actions/cache@v1 25 | with: 26 | path: ~/.android/build-cache 27 | key: ${{ runner.OS }}-android-cache 28 | 29 | - name: set up JDK 1.8 30 | uses: actions/setup-java@v1 31 | with: 32 | java-version: 1.8 33 | 34 | - name: Make gradlew executable 35 | run: chmod +x gradlew 36 | 37 | - name: Sync Gradle 38 | run: ./gradlew clean dependencies 39 | 40 | - name: Build the project 41 | run: ./gradlew build 42 | 43 | - name: Test 44 | run: ./gradlew testDebugUnitTest -------------------------------------------------------------------------------- /JsonAPI/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Users\gusta\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -keepclassmembers class com.gustavofao.jsonapi.Models.JSONApiObject { com.gustavofao.jsonapi.Models.Links links; } 19 | -keepclassmembers class com.gustavofao.jsonapi.Models.JSONList { com.gustavofao.jsonapi.Models.Links links; } 20 | -keepclassmembers class com.gustavofao.jsonapi.Models.Resource { com.gustavofao.jsonapi.Models.Links links; } 21 | -keepclassmembers class com.gustavofao.jsonapi.Models.Resource { java.lang.String id; } -------------------------------------------------------------------------------- /JsonAPI/src/test/java/com/gustavofao/jsonapi/testutils/FileLoader.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.testutils; 2 | 3 | import java.io.*; 4 | 5 | public class FileLoader { 6 | 7 | private String getContentOf(String path) throws IOException { 8 | ClassLoader classLoader = getClass().getClassLoader(); 9 | File file = new File(classLoader.getResource(path).getFile()); 10 | if (file.exists()) { 11 | BufferedReader reader = new BufferedReader(new FileReader(file)); 12 | 13 | String text; 14 | StringBuilder builder = new StringBuilder(); 15 | 16 | while ((text = reader.readLine()) != null) { 17 | if (!builder.toString().isEmpty()) { 18 | builder.append("\n"); 19 | } 20 | builder.append(text); 21 | } 22 | 23 | return builder.toString(); 24 | } else { 25 | throw new FileNotFoundException(); 26 | } 27 | } 28 | 29 | public String getContent(String path) { 30 | try { 31 | return getContentOf(path); 32 | } catch (IOException e) { 33 | e.printStackTrace(); 34 | return null; 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Models/Links.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Models; 2 | 3 | public class Links { 4 | private String self; 5 | private String first; 6 | private String prev; 7 | private String next; 8 | private String last; 9 | private String related; 10 | 11 | public String getSelf() { 12 | return self; 13 | } 14 | 15 | public void setSelf(String self) { 16 | this.self = self; 17 | } 18 | 19 | public String getFirst() { 20 | return first; 21 | } 22 | 23 | public void setFirst(String first) { 24 | this.first = first; 25 | } 26 | 27 | public String getPrev() { 28 | return prev; 29 | } 30 | 31 | public void setPrev(String prev) { 32 | this.prev = prev; 33 | } 34 | 35 | public String getNext() { 36 | return next; 37 | } 38 | 39 | public void setNext(String next) { 40 | this.next = next; 41 | } 42 | 43 | public String getLast() { 44 | return last; 45 | } 46 | 47 | public void setLast(String last) { 48 | this.last = last; 49 | } 50 | 51 | public String getRelated() { 52 | return related; 53 | } 54 | 55 | public void setRelated(String related) { 56 | this.related = related; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Models/JSONApiObject.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Models; 2 | 3 | import org.json.JSONObject; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class JSONApiObject { 9 | 10 | private List data; 11 | private Links links; 12 | private boolean hasErrors = true; 13 | private List errors; 14 | private JSONObject meta; 15 | 16 | public JSONApiObject() { 17 | data = new ArrayList<>(); 18 | } 19 | 20 | public List getData() { 21 | return data; 22 | } 23 | 24 | public T getData(int position) { 25 | return data.get(position); 26 | } 27 | 28 | public void setData(List data) { 29 | this.data = data; 30 | } 31 | 32 | public void addData(T resource) { 33 | data.add(resource); 34 | } 35 | 36 | public Links getLinks() { 37 | return links; 38 | } 39 | 40 | public void setLinks(Links links) { 41 | this.links = links; 42 | } 43 | 44 | public boolean hasErrors() { 45 | return hasErrors; 46 | } 47 | 48 | public List getErrors() { 49 | return errors; 50 | } 51 | 52 | public JSONObject getMeta() { 53 | return meta; 54 | } 55 | 56 | public void setMeta(JSONObject meta) { 57 | this.meta = meta; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Retrofit/JSONConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Retrofit; 2 | 3 | import com.gustavofao.jsonapi.JSONApiConverter; 4 | import com.gustavofao.jsonapi.Models.Resource; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.lang.reflect.Type; 8 | 9 | import okhttp3.RequestBody; 10 | import okhttp3.ResponseBody; 11 | import retrofit2.Converter; 12 | import retrofit2.Retrofit; 13 | 14 | public class JSONConverterFactory extends Converter.Factory { 15 | 16 | private JSONApiConverter jsonApiConverter; 17 | 18 | public static JSONConverterFactory create(JSONApiConverter jsonApiConverter) { 19 | return new JSONConverterFactory(jsonApiConverter); 20 | } 21 | 22 | public static JSONConverterFactory create(Class... classes) { 23 | return new JSONConverterFactory(new JSONApiConverter(classes)); 24 | } 25 | 26 | private JSONConverterFactory(JSONApiConverter jsonApiConverter) { 27 | this.jsonApiConverter = jsonApiConverter; 28 | } 29 | 30 | @Override 31 | public Converter responseBodyConverter(Type type, Annotation[] annotations, 32 | Retrofit retrofit) { 33 | return new JSONResponseBodyConverter<>(jsonApiConverter); 34 | } 35 | 36 | @Override 37 | public Converter requestBodyConverter(Type type, Annotation[] parameterAnnotations, 38 | Annotation[] methodAnnotations, Retrofit retrofit) { 39 | return new JSONRequestBodyConverter<>(jsonApiConverter); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /publish.gradle: -------------------------------------------------------------------------------- 1 | // This is just a copy from https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle 2 | // ./gradlew clean build generateRelease 3 | apply plugin: 'maven' 4 | 5 | def groupId = project.PUBLISH_GROUP_ID 6 | def artifactId = project.PUBLISH_ARTIFACT_ID 7 | def version = project.PUBLISH_VERSION 8 | 9 | def localReleaseDest = "${buildDir}/release/${version}" 10 | 11 | task androidJavadocs(type: Javadoc) { 12 | failOnError = false 13 | source = android.sourceSets.main.java.srcDirs 14 | ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" 15 | classpath += files(ext.androidJar) 16 | } 17 | 18 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 19 | classifier = 'javadoc' 20 | from androidJavadocs.destinationDir 21 | } 22 | 23 | task androidSourcesJar(type: Jar) { 24 | classifier = 'sources' 25 | from android.sourceSets.main.java.srcDirs 26 | } 27 | 28 | uploadArchives { 29 | repositories.mavenDeployer { 30 | pom.groupId = groupId 31 | pom.artifactId = artifactId 32 | pom.version = version 33 | // Add other pom properties here if you want (developer details / licenses) 34 | repository(url: "file://${localReleaseDest}") 35 | } 36 | } 37 | 38 | task zipRelease(type: Zip) { 39 | from localReleaseDest 40 | destinationDir buildDir 41 | archiveName "release-${version}.zip" 42 | } 43 | 44 | task generateRelease { 45 | doLast { 46 | println "Release ${version} can be found at ${localReleaseDest}/" 47 | println "Release ${version} zipped can be found ${buildDir}/release-${version}.zip" 48 | } 49 | } 50 | 51 | generateRelease.dependsOn(uploadArchives) 52 | generateRelease.dependsOn(zipRelease) 53 | 54 | 55 | artifacts { 56 | archives androidSourcesJar 57 | archives androidJavadocsJar 58 | } 59 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /JsonAPI/src/test/java/com/gustavofao/jsonapi/JSONApiConverterTest.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi; 2 | 3 | import com.gustavofao.jsonapi.Models.JSONApiObject; 4 | import com.gustavofao.jsonapi.Models.Links; 5 | import com.gustavofao.jsonapi.testmodels.*; 6 | import com.gustavofao.jsonapi.testutils.FileLoader; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import java.util.Date; 11 | 12 | import static org.junit.Assert.*; 13 | 14 | public class JSONApiConverterTest { 15 | 16 | private JSONApiConverter subject; 17 | private FileLoader fileLoader = new FileLoader(); 18 | 19 | @Before 20 | public void setUp() { 21 | subject = new JSONApiConverter( 22 | City.class, 23 | User.class, 24 | Person.class, 25 | Conversation.class, 26 | InvalidResource.class 27 | ); 28 | } 29 | 30 | @Test 31 | public void fromJson_shouldSucceed() { 32 | String json = fileLoader.getContent("jsons/conversation.json"); 33 | 34 | JSONApiObject result = subject.fromJson(json); 35 | 36 | assertFalse(result.hasErrors()); 37 | 38 | Conversation conversation = result.getData().get(0); 39 | assertEquals("4214653", conversation.getId()); 40 | assertEquals("content", conversation.getMessage()); 41 | assertEquals(new Date(1466014572000L), conversation.getDate()); 42 | 43 | Person person = conversation.getPerson(); 44 | assertEquals("451", person.getId()); 45 | assertEquals("Gustavo Fão Valvassori", person.getName()); 46 | 47 | Links links = result.getLinks(); 48 | assertEquals("http://example.com/articles?page[number]=3&page[size]=1", links.getSelf()); 49 | assertEquals("http://example.com/articles?page[number]=1&page[size]=1", links.getFirst()); 50 | assertEquals("http://example.com/articles?page[number]=2&page[size]=1", links.getPrev()); 51 | assertEquals("http://example.com/articles?page[number]=4&page[size]=1", links.getNext()); 52 | assertEquals("http://example.com/articles?page[number]=13&page[size]=1", links.getLast()); 53 | } 54 | 55 | @Test 56 | public void toJson_withResource_shouldSucceed() { 57 | String expectedResult = fileLoader.getContent("jsons/person.json"); 58 | 59 | Person p = new Person(); 60 | p.setId("id-01"); 61 | p.setName("Gustavo"); 62 | p.setFirstName("Gustavo"); 63 | p.setPseudo("faogustavo"); 64 | p.setCity("Santa Maria - RS, Brazil"); 65 | p.setEmail("faogustavo@gmail.com"); 66 | 67 | String result = subject.toJson(p); 68 | assertEquals(expectedResult, result); 69 | } 70 | } -------------------------------------------------------------------------------- /JsonAPI/src/test/java/com/gustavofao/jsonapi/testmodels/Person.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.testmodels; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import com.gustavofao.jsonapi.Annotations.Type; 6 | import com.gustavofao.jsonapi.Annotations.Types; 7 | import com.gustavofao.jsonapi.Models.Resource; 8 | 9 | @Types({"person", "people"}) 10 | public class Person extends Resource implements Parcelable { 11 | 12 | private String name; 13 | private String firstName; 14 | private String pseudo; 15 | private String city; 16 | private String email; 17 | 18 | public Person() {} 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public void setName(String name) { 25 | this.name = name; 26 | } 27 | 28 | public String getFirstName() { 29 | return firstName; 30 | } 31 | 32 | public void setFirstName(String firstName) { 33 | this.firstName = firstName; 34 | } 35 | 36 | public String getPseudo() { 37 | return pseudo; 38 | } 39 | 40 | public void setPseudo(String pseudo) { 41 | this.pseudo = pseudo; 42 | } 43 | 44 | public String getCity() { 45 | return city; 46 | } 47 | 48 | public void setCity(String city) { 49 | this.city = city; 50 | } 51 | 52 | public String getEmail() { 53 | return email; 54 | } 55 | 56 | public void setEmail(String email) { 57 | this.email = email; 58 | } 59 | 60 | 61 | // ============================================================================================= 62 | // Parcelable Interface 63 | 64 | public final static Creator CREATOR = new Creator() { 65 | public Person createFromParcel(final Parcel in) { 66 | return new Person(in); 67 | } 68 | 69 | public Person[] newArray(final int size) { 70 | return new Person[size]; 71 | } 72 | }; 73 | 74 | public Person(final Parcel in) { 75 | readFromParcel(in); 76 | } 77 | 78 | @Override 79 | public int describeContents() { 80 | return 0; 81 | } 82 | 83 | @Override 84 | public void writeToParcel(final Parcel dest, final int flags) { 85 | dest.writeString(getId()); 86 | dest.writeString(name); 87 | dest.writeString(firstName); 88 | dest.writeString(pseudo); 89 | dest.writeString(email); 90 | dest.writeString(city); 91 | } 92 | 93 | private void readFromParcel(final Parcel in) { 94 | setId(in.readString()); 95 | name = in.readString(); 96 | firstName = in.readString(); 97 | pseudo = in.readString(); 98 | email = in.readString(); 99 | city = in.readString(); 100 | } 101 | } -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/Models/Resource.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi.Models; 2 | 3 | import com.gustavofao.jsonapi.Annotations.Excluded; 4 | import com.gustavofao.jsonapi.Annotations.Id; 5 | import com.gustavofao.jsonapi.Annotations.Type; 6 | import com.gustavofao.jsonapi.Annotations.Types; 7 | 8 | import java.lang.reflect.Field; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | public class Resource { 14 | 15 | @Id 16 | private String id; 17 | private Links links; 18 | 19 | @Excluded 20 | private String type; 21 | 22 | @Excluded 23 | protected boolean hasAttributes; 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | public void setId(String id) { 30 | this.id = id; 31 | } 32 | 33 | public String getType() { 34 | if (type != null) 35 | return type; 36 | if (getClass().getAnnotation(Type.class) != null) 37 | return getClass().getAnnotation(Type.class).value(); 38 | if (type == null && getClass().getAnnotation(Types.class).value() != null && getClass().getAnnotation(Types.class).value().length > 0) 39 | return getClass().getAnnotation(Types.class).value()[0]; 40 | return ""; 41 | } 42 | 43 | public boolean hasAttributes() { 44 | return hasAttributes; 45 | } 46 | 47 | public Links getLinks() { 48 | return links; 49 | } 50 | 51 | public void setLinks(Links links) { 52 | this.links = links; 53 | } 54 | 55 | @Override 56 | public boolean equals(Object o) { 57 | if (o.getClass() != getClass()) 58 | return false; 59 | 60 | List fields = getFields(getClass()); 61 | for (Field f : fields) { 62 | boolean acessible = f.isAccessible(); 63 | try { 64 | f.setAccessible(true); 65 | Object v1 = f.get(this); 66 | Object v2 = f.get(o); 67 | 68 | if (v1 == null && v2 == null) 69 | continue; 70 | 71 | if ((v1 != null && v2 == null) || (v1 == null && v2 != null)) 72 | return false; 73 | 74 | if (!f.get(this).equals(f.get(o))) 75 | return false; 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | }finally { 79 | f.setAccessible(acessible); 80 | } 81 | } 82 | 83 | return true; 84 | } 85 | 86 | private static List getFields(Class type) { 87 | List fields = new ArrayList<>(); 88 | fields.addAll(Arrays.asList(type.getDeclaredFields())); 89 | 90 | if (type.getSuperclass() != null && !type.getSuperclass().getName().equals(Object.class.getName())) { 91 | fields.addAll(getFields(type.getSuperclass())); 92 | } 93 | 94 | return fields; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | xmlns:android 20 | 21 | ^$ 22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 | xmlns:.* 31 | 32 | ^$ 33 | 34 | 35 | BY_NAME 36 | 37 |
38 |
39 | 40 | 41 | 42 | .*:id 43 | 44 | http://schemas.android.com/apk/res/android 45 | 46 | 47 | 48 |
49 |
50 | 51 | 52 | 53 | .*:name 54 | 55 | http://schemas.android.com/apk/res/android 56 | 57 | 58 | 59 |
60 |
61 | 62 | 63 | 64 | name 65 | 66 | ^$ 67 | 68 | 69 | 70 |
71 |
72 | 73 | 74 | 75 | style 76 | 77 | ^$ 78 | 79 | 80 | 81 |
82 |
83 | 84 | 85 | 86 | .* 87 | 88 | ^$ 89 | 90 | 91 | BY_NAME 92 | 93 |
94 |
95 | 96 | 97 | 98 | .* 99 | 100 | http://schemas.android.com/apk/res/android 101 | 102 | 103 | ANDROID_ATTRIBUTE_ORDER 104 | 105 |
106 |
107 | 108 | 109 | 110 | .* 111 | 112 | .* 113 | 114 | 115 | BY_NAME 116 | 117 |
118 |
119 |
120 |
121 | 122 | 124 |
125 |
-------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JSONApi 2 | [![Download](https://api.bintray.com/packages/faogustavo/maven/JSONApi/images/download.svg)](https://bintray.com/faogustavo/maven/JSONApi/_latestVersion) 3 | [![License](https://img.shields.io/hexpm/l/plug.svg)]() 4 | ![MinSDK](https://img.shields.io/badge/minSdk-15-brightgreen.svg) 5 | 6 | ================================================================================================================================================================= 7 | 8 | A simple way to implement JSONApi specifications to convert Models to Json and Json to Models. 9 | 10 | ## INSTALL 11 | Add this dependecy from jCenter: 12 | 13 | ```gradle 14 | compile 'com.gustavofao:JSONApi:${latestVersion}' 15 | ``` 16 | 17 | If the installation fails, add this line to your top level gradle: 18 | 19 | ```gradle 20 | maven { url "http://dl.bintray.com/faogustavo/maven" } 21 | ``` 22 | 23 | ## USAGE 24 | The first step to use the library is to initiate the deserializer with your classes. 25 | To show how it works, we will use the default JSON that is on jsonapi.org homepage and on [raw folder](/app/src/main/res/raw/data.json). 26 | 27 | ### FIRST STEP - *Create your models* 28 | All models to be converted need to: 29 | 30 | * Inherit from [Resource](/JsonAPI/src/main/java/com/gustavofao/jsonapi/Models/Resource.java) 31 | * And have the [Type](/JsonAPI/src/main/java/com/gustavofao/jsonapi/Annotations/Type.java) annotation. 32 | 33 | > **NOTE:** 34 | * Do not include a field name "id" or "type" as those two fields are taken care of by [Resource](/JsonAPI/src/main/java/com/gustavofao/jsonapi/Models/Resource.java) and [Type](/JsonAPI/src/main/java/com/gustavofao/jsonapi/Annotations/Type.java) 35 | * Do include an empty/ zero argument constructor 36 | 37 | ```java 38 | import com.gustavofao.jsonapi.Annotatios.Type; 39 | import com.gustavofao.jsonapi.Models.Resource; 40 | 41 | @Type("comments") 42 | public class Comment extends Resource { 43 | 44 | private String body; 45 | private Person author; 46 | 47 | /* 48 | Important: If you leave this out, de-serialisation might fail "... has no zero argument constructor" 49 | If you use proguard minify, add the rule mentioned in the end of this page. 50 | */ 51 | public Comment() {} 52 | 53 | public String getBody() { 54 | return body; 55 | } 56 | 57 | public void setBody(String body) { 58 | this.body = body; 59 | } 60 | 61 | public Person getPerson() { 62 | return author; 63 | } 64 | 65 | public void setPerson(Person author) { 66 | this.author = author; 67 | } 68 | } 69 | ``` 70 | 71 | ### SECOND STEP - *Instantiate the JSONApiConverter* 72 | The JSONApiConverter have to be instantiated with all your models. 73 | 74 | ```java 75 | JSONApiConverter api = new JSONApiConverter(Article.class, Comment.class, Person.class); 76 | ``` 77 | 78 | ### THIRD STEP - *Serialize or deserialize* 79 | 80 | #### SERIALIZE INTO JSON 81 | To serialize one object, it have to be an instance or inherit from Resource and have to be passed as parameter to *toJson*. 82 | The return will be a String with the JSON. 83 | 84 | ```java 85 | Article article = new Article(); 86 | 87 | // 88 | // SET VALUES HERE 89 | // 90 | 91 | String jsonValue = api.toJson(article); 92 | ``` 93 | 94 | > **WARNING** 95 | > LINKS FIELDS ARE NOT GOING TO BE SERIALIZED. I'M WORKING ON IT FOR THE NEXT VERSION. 96 | 97 | #### DESERIALIZE FROM JSON 98 | To deserialize the JSON, you have to pass it as parameter for *fromJson* method. 99 | The return will be an JSONApiObject. 100 | 101 | ```java 102 | JSONApiObject
obj = api.fromJson(json); 103 | if (obj.getData().size() > 0) { 104 | //Success 105 | if (obj.getData().size() == 1) { 106 | //Single Object 107 | Article article = obj.getData(0); 108 | } else { 109 | //List of Objects 110 | List
resources = obj.getData(); 111 | } 112 | } else { 113 | //Error or empty data 114 | } 115 | ``` 116 | 117 | > **WARNING** 118 | > DATA WILL ALWAYS COME AS LIST. YOU HAVE TO VERIFY IF THERE IS ONLY ONE OR MORE. I'M WORKING ON IT TOO. 119 | 120 | ### TIPS 121 | #### ONE-TO-MANY RELATION 122 | To handle with one-to-many relation you have to use [JSONList](/JsonAPI/src/main/java/com/gustavofao/jsonapi/Models/JSONList.java) with the type of the Object. 123 | Example below. 124 | 125 | #### CHANGE SERIALIZATION NAME 126 | To change the name of the object on the JSON you can use the Annotation [SerialName](/JsonAPI/src/main/java/com/gustavofao/jsonapi/Annotations/SerialName.java) on your field. 127 | Example below. 128 | 129 | #### IGNORE FIELDS 130 | To ignore fields of the model you have to use the Annotation [Excluded](/JsonAPI/src/main/java/com/gustavofao/jsonapi/Annotations/Excluded.java) on your field. 131 | Example below. 132 | 133 | ```java 134 | import com.gustavofao.jsonapi.Annotatios.Excluded; 135 | import com.gustavofao.jsonapi.Annotatios.SerialName; 136 | import com.gustavofao.jsonapi.Annotatios.Type; 137 | import com.gustavofao.jsonapi.Models.JSONList; 138 | import com.gustavofao.jsonapi.Models.Resource; 139 | 140 | @Type("articles") 141 | public class Article extends Resource { 142 | 143 | private String title; 144 | private JSONList comments; 145 | 146 | @Excluded 147 | private String blah; 148 | 149 | @SerialName("author") 150 | private Person person; 151 | 152 | public String getTitle() { 153 | return title; 154 | } 155 | 156 | public void setTitle(String title) { 157 | this.title = title; 158 | } 159 | 160 | public Person getPerson() { 161 | return person; 162 | } 163 | 164 | public void setPerson(Person person) { 165 | this.person = person; 166 | } 167 | 168 | public JSONList getComments() { 169 | return comments; 170 | } 171 | 172 | public void setComments(JSONList comments) { 173 | this.comments = comments; 174 | } 175 | } 176 | 177 | ``` 178 | 179 | #### MULTIPLE TYPE TO SAME OBJECT 180 | When you have different types for the same object you can use the annotation @Types(String[] value). 181 | 182 | ```java 183 | @Types({"test", "test02"}) 184 | ``` 185 | 186 | #### METADATA 187 | Meta data can be retrieved from the JSONApiObject using getMeta() 188 | 189 | ```java 190 | JSONApiObject
obj = api.fromJson(json); 191 | JSONObject meta = obj.getMeta(); 192 | ``` 193 | 194 | ## ERRORS 195 | The documentation from errors can be found in [this link](http://jsonapi.org/examples/#error-objects-multiple-errors). 196 | To handle with it you have to check your **JSONApiObject** if it *hasErrors()*. 197 | 198 | ```java 199 | JSONApiObject obj = api.fromJson(json); 200 | if (obj.hasErrors()) { 201 | List errorList = obj.getErrors(); 202 | //Do Something with the errors 203 | } else { 204 | //Handle with success conversion 205 | } 206 | ``` 207 | 208 | The attributes from ErrorModel are: 209 | ```java 210 | private String status; 211 | private String title; 212 | private String detail; 213 | private ErrorSource source; 214 | ``` 215 | 216 | And from ErrorSource: 217 | ```java 218 | private String pointer; 219 | private String parameter; 220 | ``` 221 | 222 | ### Retrofit 223 | The library has integration with Retrofit. 224 | To use you have to pass the JSONConverterFactory as converterFactory and 225 | 226 | ```java 227 | Retrofit retrofit = new Retrofit.Builder() 228 | .addConverterFactory(JSONConverterFactory.create(Article.class, Comment.class, Person.class)) 229 | .baseUrl(url) 230 | .build(); 231 | ``` 232 | 233 | All requests have to be with parameter from server **JSONApiObject**. 234 | 235 | ```java 236 | Call obj = service.testRequest(); 237 | obj.enqueue(new Callback() { 238 | @Override 239 | public void onResponse(Call call, Response response) { 240 | if (response.body() != null) { 241 | if (response.body().hasErrors()) { 242 | List errorList = response.body().getErrors(); 243 | //Do something with the errors 244 | } else { 245 | if (response.body().getData().size() > 0) { 246 | Toast.makeText(MainActivity.this, "Object With data", Toast.LENGTH_SHORT).show(); 247 | } else { 248 | Toast.makeText(MainActivity.this, "No Items", Toast.LENGTH_SHORT).show(); 249 | } 250 | } 251 | } else { 252 | try { 253 | JSONApiObject object = App.getConverter().fromJson(response.errorBody().string()); 254 | handleErrors(object.getErrors()); 255 | } catch (IOException e) { 256 | Toast.makeText(MainActivity.this, "Empty Body", Toast.LENGTH_SHORT).show(); 257 | } 258 | } 259 | } 260 | 261 | @Override 262 | public void onFailure(Call call, Throwable t) { 263 | Toast.makeText(MainActivity.this, "Fail", Toast.LENGTH_SHORT).show(); 264 | } 265 | }); 266 | ``` 267 | 268 | ## SERIALIZATION MAPS 269 | 270 | At this moment we can do the mapping listed above (java -> Json): 271 | * String -> String 272 | * Date -> String 273 | * char -> String 274 | * double -> Double 275 | * float -> Double 276 | * int -> Integer 277 | * boolean -> Boolean 278 | * Map -> JSONObject 279 | * Resouce -> Relationship + Include 280 | 281 | 282 | ## PROGUARD 283 | 284 | If you have `minifyEnabled` on your proguard, you have to add this rule to your proguard file. 285 | This way, you will not receive an error with the message "YourResource has no zero argument constructor". 286 | 287 | ``` 288 | -keep public class * extends com.gustavofao.jsonapi.Models.Resource 289 | ``` 290 | 291 | ## Next steps 292 | - [X] Configure CI and add build badge 293 | - [X] Add unit tests 294 | - [X] Use generics to get the value 295 | - [ ] Make resources variables protected [#21](https://github.com/faogustavo/JSONApi/issues/21) 296 | - [ ] Use custom attribute decoders 297 | - [ ] Create more samples 298 | - [ ] Parse JSONObjects and arrays/lists under attributes [#12](https://github.com/faogustavo/JSONApi/issues/12) 299 | 300 | ## License 301 | Copyright 2016 Gustavo Fão. All rights reserved. 302 | 303 | Licensed under the Apache License, Version 2.0 (the "License"); 304 | you may not use this file except in compliance with the License. 305 | You may obtain a copy of the License at 306 | 307 | http://www.apache.org/licenses/LICENSE-2.0 308 | 309 | Unless required by applicable law or agreed to in writing, software 310 | distributed under the License is distributed on an "AS IS" BASIS, 311 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 312 | See the License for the specific language governing permissions and 313 | limitations under the License. 314 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /JsonAPI/src/main/java/com/gustavofao/jsonapi/JSONApiConverter.java: -------------------------------------------------------------------------------- 1 | package com.gustavofao.jsonapi; 2 | 3 | import android.util.Log; 4 | 5 | import com.gustavofao.jsonapi.Annotations.Excluded; 6 | import com.gustavofao.jsonapi.Annotations.Id; 7 | import com.gustavofao.jsonapi.Annotations.SerialName; 8 | import com.gustavofao.jsonapi.Annotations.Type; 9 | import com.gustavofao.jsonapi.Annotations.Types; 10 | import com.gustavofao.jsonapi.Models.ErrorModel; 11 | import com.gustavofao.jsonapi.Models.ErrorSource; 12 | import com.gustavofao.jsonapi.Models.JSONApiObject; 13 | import com.gustavofao.jsonapi.Models.JSONList; 14 | import com.gustavofao.jsonapi.Models.Links; 15 | import com.gustavofao.jsonapi.Models.Resource; 16 | 17 | import org.json.JSONArray; 18 | import org.json.JSONException; 19 | import org.json.JSONObject; 20 | 21 | import java.lang.reflect.Field; 22 | import java.text.ParseException; 23 | import java.text.SimpleDateFormat; 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | import java.util.Collection; 27 | import java.util.Date; 28 | import java.util.HashMap; 29 | import java.util.Iterator; 30 | import java.util.List; 31 | import java.util.Locale; 32 | import java.util.Map; 33 | 34 | public class JSONApiConverter { 35 | 36 | private static final String DATE_FORMAT_SERVER = "yyyy-MM-dd'T'HH:mm:ssZ"; 37 | 38 | private SimpleDateFormat dateFormatter; 39 | 40 | private HashMap> classesIndex; 41 | 42 | public JSONApiConverter(Class... classes) { 43 | dateFormatter = new SimpleDateFormat(DATE_FORMAT_SERVER, Locale.US); 44 | 45 | classesIndex = new HashMap<>(); 46 | List> classList = Arrays.asList(classes); 47 | 48 | for (Class c : classList) { 49 | if (c.getAnnotation(Type.class) != null) { 50 | String type = c.getAnnotation(Type.class).value(); 51 | if (!classesIndex.containsKey(type)) 52 | classesIndex.put(type, c); 53 | } else if (c.getAnnotation(Types.class) != null) { 54 | String[] types = c.getAnnotation(Types.class).value(); 55 | for (String type : types) { 56 | if (!classesIndex.containsKey(type)) 57 | classesIndex.put(type, c); 58 | } 59 | } else { 60 | Log.e("Classes", "No Annotation [" + c.getName() + "]"); 61 | } 62 | } 63 | } 64 | 65 | public JSONApiConverter(HashMap> classesIndex) { 66 | dateFormatter = new SimpleDateFormat(DATE_FORMAT_SERVER, Locale.US); 67 | this.classesIndex = classesIndex; 68 | } 69 | 70 | public JSONApiConverter withDateFormat(SimpleDateFormat dateFormat) { 71 | this.dateFormatter = dateFormat; 72 | return this; 73 | } 74 | 75 | public JSONApiConverter withDateFormat(String format) { 76 | this.dateFormatter = new SimpleDateFormat(format, Locale.US); 77 | return this; 78 | } 79 | 80 | private Date parseDate(String date) throws ParseException { 81 | return dateFormatter.parse(date); 82 | } 83 | 84 | public String toJson(Resource resource) { 85 | JSONObject json = toJsonObject(resource); 86 | if (json != null) 87 | return json.toString(); 88 | return null; 89 | } 90 | 91 | public String toJson(List resources) { 92 | JSONObject json = toJsonArray(resources); 93 | if (json != null) 94 | return json.toString(); 95 | return null; 96 | } 97 | 98 | public JSONApiObject fromJson(String jsonObject) { 99 | JSONApiObject jsonApiObject = new JSONApiObject(); 100 | try { 101 | JSONObject json = new JSONObject(jsonObject); 102 | HashMap includes = new HashMap<>(); 103 | 104 | if (json.isNull("errors")) { 105 | if (!json.isNull("included")) { 106 | JSONArray included = json.getJSONArray("included"); 107 | for (int i = 0; i < included.length(); i++) { 108 | JSONObject each = included.getJSONObject(i); 109 | String key = getResourceTag(each); 110 | 111 | includes.put(key, resourceFromJson(each, includes)); 112 | } 113 | 114 | //Second pass to check if there is this item on relationships 115 | for (int i = 0; i < included.length(); i++) { 116 | JSONObject each = included.getJSONObject(i); 117 | String key = getResourceTag(each); 118 | 119 | includes.remove(key); 120 | includes.put(key, resourceFromJson(each, includes)); 121 | } 122 | } 123 | 124 | if (!json.isNull("data")) { 125 | Object data = json.get("data"); 126 | if (data instanceof JSONObject) { 127 | //Single Object 128 | JSONObject objectData = (JSONObject) data; 129 | jsonApiObject.addData(resourceFromJson(objectData, includes)); 130 | } else if (data instanceof JSONArray) { 131 | //ListObjects 132 | JSONArray objectData = (JSONArray) data; 133 | for (int i = 0; i < objectData.length(); i++) { 134 | jsonApiObject.addData(resourceFromJson(objectData.getJSONObject(i), includes)); 135 | } 136 | } 137 | } 138 | 139 | if (!json.isNull("links")) 140 | jsonApiObject.setLinks(linksFromJson(json.getJSONObject("links"))); 141 | 142 | if (!json.isNull("meta")) 143 | jsonApiObject.setMeta(json.getJSONObject("meta")); 144 | 145 | Field hasErrors = jsonApiObject.getClass().getDeclaredField("hasErrors"); 146 | boolean oldAcessible = hasErrors.isAccessible(); 147 | hasErrors.setAccessible(true); 148 | hasErrors.setBoolean(jsonApiObject, false); 149 | hasErrors.setAccessible(oldAcessible); 150 | } else { 151 | JSONArray errorList = json.getJSONArray("errors"); 152 | List errorModelList = new ArrayList<>(); 153 | for (int i = 0; i < errorList.length(); i++) { 154 | ErrorModel error = new ErrorModel(); 155 | JSONObject eachRow = errorList.getJSONObject(i); 156 | 157 | if (!eachRow.isNull("code")) 158 | error.setStatus(eachRow.getString("code")); 159 | 160 | if (!eachRow.isNull("detail")) 161 | error.setDetail(eachRow.getString("detail")); 162 | 163 | if (!eachRow.isNull("title")) 164 | error.setTitle(eachRow.getString("title")); 165 | 166 | if (!eachRow.isNull("source")) { 167 | ErrorSource source = new ErrorSource(); 168 | JSONObject sourceJson = eachRow.getJSONObject("source"); 169 | 170 | if (!sourceJson.isNull("parameter")) 171 | source.setParameter(sourceJson.getString("parameter")); 172 | 173 | if (!sourceJson.isNull("pointer")) 174 | source.setPointer(sourceJson.getString("pointer")); 175 | 176 | error.setSource(source); 177 | } 178 | 179 | errorModelList.add(error); 180 | } 181 | 182 | Field hasErrors = jsonApiObject.getClass().getDeclaredField("errors"); 183 | boolean oldAcessible = hasErrors.isAccessible(); 184 | hasErrors.setAccessible(true); 185 | hasErrors.set(jsonApiObject, errorModelList); 186 | hasErrors.setAccessible(oldAcessible); 187 | } 188 | } catch (Exception ex) { 189 | Log.e(JSONApiConverter.class.getSimpleName(), "Here _ " + ex.getMessage()); 190 | ex.printStackTrace(); 191 | } 192 | return jsonApiObject; 193 | } 194 | 195 | private Resource resourceFromJson(JSONObject jsonObject, HashMap includes) 196 | throws JSONException, IllegalAccessException, InstantiationException, ParseException, NoSuchFieldException { 197 | String typeString = jsonObject.getString("type"); 198 | Resource resource = classesIndex.get(typeString).newInstance(); 199 | 200 | Field idMainField = Resource.class.getDeclaredField("id"); 201 | Field typeMainField = Resource.class.getDeclaredField("type"); 202 | Field attrMainField = Resource.class.getDeclaredField("hasAttributes"); 203 | 204 | boolean oldIdAccessible = idMainField.isAccessible(); 205 | boolean oldTypeAccessible = typeMainField.isAccessible(); 206 | boolean oldAccessibleAccessible = attrMainField.isAccessible(); 207 | 208 | idMainField.setAccessible(true); 209 | if (!jsonObject.isNull("id")) { 210 | idMainField.set(resource, jsonObject.getString("id")); 211 | } else { 212 | idMainField.set(resource, ""); 213 | } 214 | idMainField.setAccessible(oldIdAccessible); 215 | 216 | typeMainField.setAccessible(true); 217 | typeMainField.set(resource, typeString); 218 | typeMainField.setAccessible(oldTypeAccessible); 219 | 220 | attrMainField.setAccessible(true); 221 | attrMainField.setBoolean(resource, jsonObject.isNull("attributes")); 222 | attrMainField.setAccessible(oldAccessibleAccessible); 223 | 224 | //Pega os atributos da classe 225 | List fields = getFields(resource.getClass()); 226 | HashMap fieldsHash = new HashMap<>(); 227 | 228 | for (Field f : fields) { 229 | if (f.getAnnotation(SerialName.class) != null) 230 | fieldsHash.put(f.getAnnotation(SerialName.class).value(), f); 231 | else 232 | fieldsHash.put(f.getName(), f); 233 | } 234 | 235 | if (!jsonObject.isNull("attributes")) { 236 | //Separa os atributos do JSON 237 | JSONObject attributes = jsonObject.getJSONObject("attributes"); 238 | Iterator it = attributes.keys(); 239 | 240 | while (it.hasNext()) { 241 | String attr = it.next(); 242 | Object value = attributes.get(attr); 243 | 244 | if (fieldsHash.containsKey(attr)) { 245 | Field currentField = fieldsHash.get(attr); 246 | Boolean oldAccessible = currentField.isAccessible(); 247 | currentField.setAccessible(true); 248 | 249 | try { 250 | if (value instanceof String) { 251 | if (currentField.getType().equals(char.class)) 252 | currentField.setChar(resource, String.valueOf(value).charAt(0)); 253 | else if (currentField.getType().equals(String.class)) 254 | currentField.set(resource, value); 255 | else if (currentField.getType().equals(Date.class)) 256 | currentField.set(resource, parseDate(String.valueOf(value))); 257 | else if (currentField.getType().equals(double.class) || currentField.getType().equals(Double.class)) 258 | currentField.set(resource, Double.valueOf(String.valueOf(value))); 259 | else if (currentField.getType().equals(float.class) || currentField.getType().equals(Float.class)) 260 | currentField.set(resource, Float.valueOf(String.valueOf(value))); 261 | else 262 | Log.e("JSONApiConverter", "Type not setted (" + currentField.getType().getName() + ")"); 263 | } else if (value instanceof Double || value instanceof Float) { 264 | if (currentField.getType().equals(float.class) || currentField.getType().equals(Float.class)) 265 | currentField.setDouble(resource, (float) value); 266 | else if (currentField.getType().equals(double.class) || currentField.getType().equals(Double.class)) 267 | currentField.setDouble(resource, (double) value); 268 | } else if (value instanceof Integer) { 269 | currentField.setInt(resource, (int) value); 270 | } else if (value instanceof Long) { 271 | currentField.setLong(resource, (long) value); 272 | } else if (value instanceof Character) { 273 | currentField.setChar(resource, (char) value); 274 | } else if (value instanceof Boolean) { 275 | currentField.setBoolean(resource, (boolean) value); 276 | } else if (value instanceof JSONObject) { 277 | JSONObject json = (JSONObject) value; 278 | Map data = new HashMap<>(); 279 | 280 | Iterator keys = json.keys(); 281 | while (keys.hasNext()) { 282 | String k = keys.next(); 283 | data.put(k, json.get(k)); 284 | } 285 | 286 | currentField.set(resource, data); 287 | } else if (value instanceof JSONArray) { 288 | JSONArray array = (JSONArray) value; 289 | List arrayData = new ArrayList<>(); 290 | if (array.length() > 0) { 291 | for (int i = 0; i < array.length(); i++) { 292 | arrayData.add(array.get(i)); 293 | } 294 | } 295 | currentField.set(resource, arrayData); 296 | } 297 | } catch (Exception ex) { 298 | Log.e("JSONApiConverter", String.format("Failed to pass attribute %s", attr)); 299 | } 300 | 301 | currentField.setAccessible(oldAccessible); 302 | } 303 | } 304 | } 305 | 306 | //Termina os atributos 307 | //Inicia os relationships 308 | if (!jsonObject.isNull("relationships")) { 309 | JSONObject relationships = jsonObject.getJSONObject("relationships"); 310 | Iterator keys = relationships.keys(); 311 | while (keys.hasNext()) { 312 | String key = keys.next(); 313 | JSONObject eachRelation = relationships.getJSONObject(key); 314 | 315 | Object data = eachRelation.get("data"); 316 | 317 | if (data == null || data == JSONObject.NULL) { 318 | //Forcing the data to be an actual object in order to keep parsing the links on data:null 319 | data = new JSONArray(); 320 | } 321 | 322 | if (fieldsHash.containsKey(key)){ 323 | Field field = fieldsHash.get(key); 324 | Boolean oldAccessible = field.isAccessible(); 325 | field.setAccessible(true); 326 | 327 | if (data instanceof JSONObject) { 328 | JSONObject dataJson = (JSONObject) data; 329 | String keyRelation = getResourceTag(dataJson); 330 | Object fieldValue = null; 331 | 332 | if (includes.get(keyRelation) != null) { 333 | fieldValue = includes.get(keyRelation); 334 | } else { 335 | if (dataJson.isNull("attributes")) { 336 | String id = dataJson.getString("id"); 337 | String type = dataJson.getString("type"); 338 | 339 | fieldValue = classesIndex.get(type).newInstance(); 340 | 341 | Field idField = Resource.class.getDeclaredField("id"); 342 | boolean oldAcessible = idField.isAccessible(); 343 | 344 | idField.setAccessible(true); 345 | idField.set(fieldValue, id); 346 | idField.setAccessible(oldAcessible); 347 | } else { 348 | fieldValue = resourceFromJson(dataJson, includes); 349 | } 350 | } 351 | 352 | if (fieldValue != null) { 353 | if (!dataJson.isNull("links")) { 354 | fieldValue.getClass().getDeclaredField("links") 355 | .set(fieldValue, linksFromJson(((JSONObject) data) 356 | .getJSONObject("links"))); 357 | } 358 | 359 | field.set(resource, fieldValue); 360 | } 361 | } else if (data instanceof JSONArray) { 362 | JSONList relationList = new JSONList<>(); 363 | JSONArray dataJson = (JSONArray) data; 364 | 365 | for (int i = 0; i < dataJson.length(); i++) { 366 | String keyRelation = getResourceTag(dataJson.getJSONObject(i)); 367 | if (includes.get(keyRelation) != null) { 368 | relationList.add(includes.get(keyRelation)); 369 | } else { 370 | if (dataJson.getJSONObject(i).isNull("attributes")) { 371 | String id = dataJson.getJSONObject(i).getString("id"); 372 | String type = dataJson.getJSONObject(i).getString("type"); 373 | 374 | Resource fieldValue = classesIndex.get(type).newInstance(); 375 | 376 | Field idField = Resource.class.getDeclaredField("id"); 377 | boolean oldAcessible = idField.isAccessible(); 378 | 379 | idField.setAccessible(true); 380 | idField.set(fieldValue, id); 381 | idField.setAccessible(oldAcessible); 382 | 383 | relationList.add(fieldValue); 384 | } else { 385 | relationList.add(resourceFromJson(dataJson.getJSONObject(i), includes)); 386 | } 387 | } 388 | } 389 | 390 | if (!eachRelation.isNull("links")) { 391 | relationList.setLinks(linksFromJson(eachRelation.getJSONObject("links"))); 392 | } 393 | 394 | field.set(resource, relationList); 395 | } 396 | field.setAccessible(oldAccessible); 397 | } 398 | } 399 | } 400 | 401 | return resource; 402 | } 403 | 404 | private Links linksFromJson(JSONObject linksJson) throws NoSuchFieldException, JSONException, IllegalAccessException { 405 | Links links = new Links(); 406 | 407 | Iterator it = linksJson.keys(); 408 | while (it.hasNext()) { 409 | String next = it.next(); 410 | if (links.getClass().getDeclaredField(next) != null) { 411 | Field currentField = links.getClass().getDeclaredField(next); 412 | 413 | Boolean oldAccessible = currentField.isAccessible(); 414 | currentField.setAccessible(true); 415 | 416 | currentField.set(links, linksJson.getString(next)); 417 | 418 | currentField.setAccessible(oldAccessible); 419 | } 420 | } 421 | 422 | return links; 423 | } 424 | 425 | private JSONObject toJsonArray(List resources) { 426 | JSONObject mainNode = new JSONObject(); 427 | try { 428 | JSONArray mainContent = new JSONArray(); 429 | JSONArray include = new JSONArray(); 430 | 431 | //IncludedIds 432 | List includedIds = new ArrayList<>(); 433 | 434 | //Handle Serialization 435 | for (Resource resource : resources) { 436 | JSONObject obj = toJsonObject(resource); 437 | 438 | //Add all data 439 | JSONObject data = obj.getJSONObject("data"); 440 | mainContent.put(data); 441 | 442 | //Add all Included 443 | if (!obj.isNull("included")) { 444 | JSONArray extras = obj.getJSONArray("included"); 445 | for (int i = 0; i < extras.length(); i++) { 446 | JSONObject eachInclude = extras.getJSONObject(i); 447 | String key = getResourceTag(eachInclude); 448 | 449 | if (!includedIds.contains(key)) { 450 | includedIds.add(key); 451 | include.put(eachInclude); 452 | } 453 | } 454 | } 455 | } 456 | 457 | mainNode.put("data", mainContent); 458 | if (include.length() > 0) 459 | mainNode.put("included", include); 460 | } catch (Exception ex){ 461 | ex.printStackTrace(); 462 | } 463 | return mainNode; 464 | } 465 | 466 | private JSONObject toJsonObject(Resource resource) { 467 | try { 468 | Resource ninstance = resource.getClass().newInstance(); 469 | if (resource.equals(ninstance)) { 470 | return new JSONObject(); 471 | } 472 | } catch (Exception e) {} 473 | 474 | List fields = getFields(resource.getClass()); 475 | JSONObject mainNode = new JSONObject(); 476 | 477 | try { 478 | JSONObject content = new JSONObject(); 479 | JSONObject attributes = new JSONObject(); 480 | JSONObject relationship = new JSONObject(); 481 | JSONArray include = new JSONArray(); 482 | 483 | //Processar os dados 484 | content.put("type", resource.getType()); 485 | for (Field field : fields) { 486 | String fieldName = null; 487 | Boolean oldAccessible = field.isAccessible(); 488 | field.setAccessible(true); 489 | 490 | if (field.getAnnotation(Excluded.class) != null) 491 | continue; 492 | 493 | if (field.get(resource) == null) 494 | continue; 495 | 496 | if (field.getAnnotation(SerialName.class) != null) { 497 | fieldName = field.getAnnotation(SerialName.class).value(); 498 | } 499 | 500 | if (fieldName == null) 501 | fieldName = field.getName(); 502 | 503 | if (Collection.class.isAssignableFrom(field.getType())) { 504 | JSONArray array = new JSONArray(); 505 | List list = (List) field.get(resource); 506 | 507 | if (list.size() > 0) { 508 | for (Object listItem : list) { 509 | try { 510 | if (listItem instanceof String) { 511 | array.put(String.valueOf(listItem)); 512 | } else if (listItem instanceof Integer) { 513 | array.put((int) listItem); 514 | } else if (listItem instanceof Double || listItem instanceof Float) { 515 | array.put(Double.valueOf(String.valueOf(listItem))); 516 | } else if (listItem instanceof Long) { 517 | array.put((Long) listItem); 518 | } else if (listItem instanceof Character) { 519 | array.put(Character.toString((char) listItem)); 520 | } else if (listItem instanceof Boolean) { 521 | array.put((boolean) listItem); 522 | } else if (listItem instanceof Date) { 523 | array.put(dateFormatter.format((Date) field.get(resource)).replace(" ", "T")); 524 | } else if (listItem instanceof Map) { 525 | JSONObject obj = new JSONObject(); 526 | Map data = (Map) field.get(listItem); 527 | 528 | Iterator it = data.keySet().iterator(); 529 | while (it.hasNext()) { 530 | String key = it.next(); 531 | obj.put(key, data.get(key)); 532 | } 533 | array.put(obj); 534 | } else if (listItem instanceof Resource) { 535 | JSONObject relationshipNode = null; 536 | JSONArray relationshipNodeData = null; 537 | 538 | if (relationship.isNull(fieldName)) { 539 | relationshipNode = new JSONObject(); 540 | } else { 541 | relationshipNode = relationship.getJSONObject(fieldName); 542 | } 543 | 544 | if (relationshipNode.isNull("data")) { 545 | relationshipNodeData = new JSONArray(); 546 | } else { 547 | relationshipNodeData = relationshipNode.getJSONArray("data"); 548 | } 549 | 550 | relationshipNodeData.put(getNodeAsRelationship(listItem)); 551 | relationshipNode.put("data", relationshipNodeData); 552 | 553 | relationship.put(fieldName, relationshipNode); 554 | 555 | JSONObject obj = getNodeAsInclude(listItem); 556 | if (obj != null) 557 | include.put(obj); 558 | } 559 | } catch (Exception e) { 560 | e.printStackTrace(); 561 | } 562 | } 563 | } 564 | 565 | if (array.length() > 0) 566 | attributes.put(fieldName, array); 567 | } else { 568 | try { 569 | if (field.getAnnotation(Id.class) != null) { 570 | content.put("id", String.valueOf(field.get(resource))); 571 | } else if (field.get(resource) instanceof String) { 572 | attributes.put(fieldName, String.valueOf(field.get(resource))); 573 | } else if (field.get(resource) instanceof Integer) { 574 | attributes.put(fieldName, field.getInt(resource)); 575 | } else if (field.get(resource) instanceof Double || field.get(resource) instanceof Float) { 576 | attributes.put(fieldName, Double.valueOf(String.valueOf(field.get(resource)))); 577 | } else if (field.get(resource) instanceof Long) { 578 | attributes.put(fieldName, field.getLong(resource)); 579 | } else if (field.get(resource) instanceof Character) { 580 | attributes.put(fieldName, Character.toString(field.getChar(resource))); 581 | } else if (field.get(resource) instanceof Boolean) { 582 | attributes.put(fieldName, field.getBoolean(resource)); 583 | } else if (field.get(resource) instanceof Date) { 584 | attributes.put(fieldName, dateFormatter.format((Date) field.get(resource)).replace(" ", "T")); 585 | } else if (field.get(resource) instanceof Map) { 586 | JSONObject obj = new JSONObject(); 587 | Map data = (Map) field.get(resource); 588 | 589 | Iterator it = data.keySet().iterator(); 590 | while (it.hasNext()) { 591 | String key = it.next(); 592 | obj.put(key, data.get(key)); 593 | } 594 | attributes.put(fieldName, obj); 595 | } else if (field.get(resource) instanceof Resource) { 596 | JSONObject relationshipNode = new JSONObject(); 597 | relationshipNode.put("data", getNodeAsRelationship(field.get(resource))); 598 | 599 | relationship.put(fieldName, relationshipNode); 600 | 601 | JSONObject obj = getNodeAsInclude(field.get(resource)); 602 | if (obj != null) 603 | include.put(obj); 604 | } 605 | } catch (Exception ex) { 606 | Log.e("JSONApiConverter", String.format("Failed to pass attribute %s", fieldName)); 607 | } 608 | } 609 | 610 | 611 | field.setAccessible(oldAccessible); 612 | } 613 | 614 | if (attributes.length() > 0) 615 | content.put("attributes", attributes); 616 | 617 | if (relationship.length() > 0) 618 | content.put("relationships", relationship); 619 | 620 | if (include.length() > 0) 621 | mainNode.put("included", include); 622 | 623 | mainNode.put("data", content); 624 | 625 | } catch (Exception ex) { 626 | //Tratar erros 627 | ex.printStackTrace(); 628 | } 629 | 630 | return mainNode; 631 | } 632 | 633 | private String getResourceTag(Resource resource) { 634 | String type = resource.getType(); 635 | return (type + "|" + resource.getId()); 636 | } 637 | 638 | private String getResourceTag(JSONObject json) throws JSONException { 639 | return (json.getString("type") + "|" + json.getString("id")); 640 | } 641 | 642 | private JSONObject getNodeAsRelationship(Object resource) throws JSONException, IllegalAccessException { 643 | List fields = getFields(resource.getClass()); 644 | JSONObject jsonObject = new JSONObject(); 645 | 646 | jsonObject.put("type", ((Resource) resource).getType()); 647 | 648 | for (Field field : fields) { 649 | Boolean oldAccessible = field.isAccessible(); 650 | field.setAccessible(true); 651 | if (field.getAnnotation(Id.class) != null) { 652 | jsonObject.put("id", field.get(resource)); 653 | } 654 | field.setAccessible(oldAccessible); 655 | } 656 | 657 | return jsonObject; 658 | } 659 | 660 | private JSONObject getNodeAsInclude(Object resource) throws JSONException, IllegalAccessException, ParseException { 661 | List fields = getFields(resource.getClass()); 662 | 663 | JSONObject content = new JSONObject(); 664 | JSONObject attributes = new JSONObject(); 665 | JSONObject relationship = new JSONObject(); 666 | 667 | content.put("type", ((Resource) resource).getType()); 668 | 669 | for (Field field : fields) { 670 | if (Collection.class.isAssignableFrom(field.getType())) 671 | continue; 672 | 673 | String fieldName = null; 674 | Boolean oldAccessible = field.isAccessible(); 675 | field.setAccessible(true); 676 | 677 | if (field.getAnnotation(Excluded.class) != null) 678 | continue; 679 | 680 | if (field.getAnnotation(SerialName.class) != null) { 681 | fieldName = field.getAnnotation(SerialName.class).value(); 682 | } 683 | 684 | if (fieldName == null) 685 | fieldName = field.getName(); 686 | 687 | if (field.getAnnotation(Id.class) != null) { 688 | content.put("id", String.valueOf(field.get(resource))); 689 | } else if (field.get(resource) instanceof String) { 690 | attributes.put(fieldName, String.valueOf(field.get(resource))); 691 | } else if (field.get(resource) instanceof Integer) { 692 | attributes.put(fieldName, field.getInt(resource)); 693 | } else if (field.get(resource) instanceof Double || field.get(resource) instanceof Float) { 694 | attributes.put(fieldName, Double.parseDouble(String.valueOf(field.get(resource)))); 695 | } else if (field.get(resource) instanceof Long) { 696 | attributes.put(fieldName, field.getLong(resource)); 697 | } else if (field.get(resource) instanceof Character) { 698 | attributes.put(fieldName, Character.toString(field.getChar(resource))); 699 | } else if (field.get(resource) instanceof Boolean) { 700 | attributes.put(fieldName, field.getBoolean(resource)); 701 | } else if (field.get(resource) instanceof Date) { 702 | attributes.put(fieldName, dateFormatter.format((Date) field.get(resource)).replace(" ", "T")); 703 | }else if (field.get(resource) instanceof Resource) { 704 | JSONObject relationshipNode = new JSONObject(); 705 | relationshipNode.put("data", getNodeAsRelationship(field.get(resource))); 706 | 707 | relationship.put(fieldName, relationshipNode); 708 | } 709 | 710 | field.setAccessible(oldAccessible); 711 | } 712 | 713 | if (attributes.length() > 0) 714 | content.put("attributes", attributes); 715 | 716 | if (relationship.length() > 0) 717 | content.put("relationships", relationship); 718 | 719 | if (attributes.length() > 0 || relationship.length() > 0) 720 | return content; 721 | 722 | return null; 723 | } 724 | 725 | private List getFields(Class type) { 726 | List fields = new ArrayList<>(); 727 | fields.addAll(Arrays.asList(type.getDeclaredFields())); 728 | 729 | if (type.getSuperclass() != null && !type.getSuperclass().getName().equals(Object.class.getName())) { 730 | fields.addAll(getFields(type.getSuperclass())); 731 | } 732 | 733 | return fields; 734 | } 735 | 736 | } 737 | --------------------------------------------------------------------------------