Merge pull request #120 from square/jwilson_0111_more_docos

Document @Json and @JsonQualifier in the README.
This commit is contained in:
Jake Wharton
2016-01-11 00:30:31 -08:00
5 changed files with 332 additions and 95 deletions

139
README.md
View File

@@ -134,13 +134,13 @@ Voila:
#### Another example #### Another example
Note that the method annotated with `@FromJson` does not need to take a String as an argument. Rather it can take Note that the method annotated with `@FromJson` does not need to take a String as an argument.
input of any type and Moshi will first parse the JSON to an object of that type and then use the `@FromJson` Rather it can take input of any type and Moshi will first parse the JSON to an object of that type
method to produce the desired final value. Conversely, the method annotated with `@ToJson` does not have to produce and then use the `@FromJson` method to produce the desired final value. Conversely, the method
a String. annotated with `@ToJson` does not have to produce a String.
Assume, for example, that we have to parse a JSON in which the date and time of an event are represented as two Assume, for example, that we have to parse a JSON in which the date and time of an event are
separate strings. represented as two separate strings.
```json ```json
{ {
@@ -151,8 +151,8 @@ separate strings.
``` ```
We would like to combine these two fields into one string to facilitate the date parsing at a We would like to combine these two fields into one string to facilitate the date parsing at a
later point. Also, we would like to have all variable names in CamelCase. Therefore, the `Event` class we later point. Also, we would like to have all variable names in CamelCase. Therefore, the `Event`
want Moshi to produce like this: class we want Moshi to produce like this:
```java ```java
class Event { class Event {
@@ -161,8 +161,9 @@ class Event {
} }
``` ```
Instead of manually parsing the JSON line per line (which we could also do) we can have Moshi Instead of manually parsing the JSON line per line (which we could also do) we can have Moshi do the
do the transformation automatically. We simply define another class `EventJson` that directly corresponds to the JSON structure: transformation automatically. We simply define another class `EventJson` that directly corresponds
to the JSON structure:
```java ```java
class EventJson { class EventJson {
@@ -172,30 +173,28 @@ class EventJson {
} }
``` ```
And another class with the appropriate `@FromJson` and `@ToJson` methods that are telling Moshi how to convert And another class with the appropriate `@FromJson` and `@ToJson` methods that are telling Moshi how
an `EventJson` to an `Event` and back. Now, whenever we are asking Moshi to parse a JSON to an `Event` it will first parse it to an `EventJson` as an intermediate step. Conversely, to serialize an `Event` Moshi will first to convert an `EventJson` to an `Event` and back. Now, whenever we are asking Moshi to parse a JSON
create an `EventJson` object and then serialize that object as usual. to an `Event` it will first parse it to an `EventJson` as an intermediate step. Conversely, to
serialize an `Event` Moshi will first create an `EventJson` object and then serialize that object as
usual.
```java ```java
class EventJsonAdapter { class EventJsonAdapter {
@FromJson Event eventFromJson(EventJson eventJson) {
@FromJson
Event eventFromJson(EventJson eventJson) {
Event event = new Event(); Event event = new Event();
event.title = eventJson.title; event.title = eventJson.title;
event.beginDateAndTime = eventJson.begin_date + " " + eventJson.begin_time; event.beginDateAndTime = eventJson.begin_date + " " + eventJson.begin_time;
return event; return event;
} }
@ToJson @ToJson EventJson eventToJson(Event event) {
EventJson eventToJson(Event event) {
EventJson json = new EventJson(); EventJson json = new EventJson();
json.title = event.title; json.title = event.title;
json.begin_date = event.beginDateAndTime.substring(0, 8); json.begin_date = event.beginDateAndTime.substring(0, 8);
json.begin_time = event.beginDateAndTime.substring(9, 14); json.begin_time = event.beginDateAndTime.substring(9, 14);
return json; return json;
} }
} }
``` ```
@@ -258,6 +257,108 @@ But the two libraries have a few important differences:
encoded in HTML without additional escaping. Moshi encodes it naturally (as `=`) and assumes that encoded in HTML without additional escaping. Moshi encodes it naturally (as `=`) and assumes that
the HTML encoder if there is one will do its job. the HTML encoder if there is one will do its job.
### Custom field names with @Json
Moshi works best when your JSON objects and Java objects have the same structure. But when they
don't, Moshi has annotations to customize data binding.
Use `@Json` to specify how Java fields map to JSON names. This is necessary when the JSON name
contains spaces or other characters that arent permitted in Java field names. For example, this
JSON has a field name containing a space:
```json
{
"username": "jesse",
"lucky number": 32
}
```
With `@Json` its corresponding Java class is easy:
```java
class Player {
String username;
@Json(name = "lucky number") int luckyNumber;
...
}
```
Because JSON field names are always defined with their Java fields, Moshi makes it easy to find
fields when navigating between Java and JSON.
### Alternate type adapters with @JsonQualifier
Use `@JsonQualifier` to customize how a type is encoded for some fields without changing its
encoding everywhere. This works similarly to the qualifier annotations in dependency injection
tools like Dagger and Guice.
Heres a JSON message with two integers and a color:
```json
{
"width": 1024,
"height": 768,
"color": "#ff0000"
}
```
By convention, Android programs also use `int` for colors:
```java
class Rectangle {
int width;
int height;
int color;
}
```
But if we encoded the above Java class as JSON, the color isn't encoded properly!
```json
{
"width": 1024,
"height": 768,
"color": 16711680
}
```
The fix is to define a qualifier annotation, itself annotated `@JsonQualifier`:
```java
@Retention(RUNTIME)
@JsonQualifier
public @interface HexColor {
}
```
Next apply this `@HexColor` annotation to the appropriate field:
```
class Rectangle {
int width;
int height;
@HexColor int color;
}
```
And finally define a type adapter to handle it:
```
/** Converts strings like #ff0000 to the corresponding color ints. */
class ColorAdapter {
@ToJson String toJson(@HexColor int rgb) {
return String.format("#%06x", rgb);
}
@FromJson @HexColor int fromJson(String rgb) {
return Integer.parseInt(rgb.substring(1), 16);
}
}
```
Use `@JsonQualifier` when you need different JSON encodings for the same type. Most programs
shouldnt need this `@JsonQualifier`, but its very handy for those that do.
Download Download
-------- --------

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2016 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.moshi.recipes;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.recipes.models.Player;
public final class CustomFieldName {
public void run() throws Exception {
String json = ""
+ "{"
+ " \"username\": \"jesse\","
+ " \"lucky number\": 32"
+ "}\n";
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Player> jsonAdapter = moshi.adapter(Player.class);
Player player = jsonAdapter.fromJson(json);
System.out.println(player);
}
public static void main(String[] args) throws Exception {
new CustomFieldName().run();
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2016 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.moshi.recipes;
import com.squareup.moshi.FromJson;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.JsonQualifier;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.ToJson;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
public final class CustomQualifier {
public void run() throws Exception {
String json = ""
+ "{\n"
+ " \"color\": \"#ff0000\",\n"
+ " \"height\": 768,\n"
+ " \"width\": 1024\n"
+ "}\n";
Moshi moshi = new Moshi.Builder()
.add(new ColorAdapter())
.build();
JsonAdapter<Rectangle> jsonAdapter = moshi.adapter(Rectangle.class);
Rectangle rectangle = jsonAdapter.fromJson(json);
System.out.println(rectangle);
}
public static void main(String[] args) throws Exception {
new CustomQualifier().run();
}
static class Rectangle {
int width;
int height;
@HexColor int color;
@Override public String toString() {
return String.format("%dx%d #%06x", width, height, color);
}
}
@Retention(RUNTIME)
@JsonQualifier
public @interface HexColor {
}
static class ColorAdapter {
@ToJson String toJson(@HexColor int rgb) {
return String.format("#%06x", rgb);
}
@FromJson @HexColor int fromJson(String rgb) {
return Integer.parseInt(rgb.substring(1), 16);
}
}
}

View File

@@ -21,12 +21,10 @@ import com.squareup.moshi.Moshi;
import com.squareup.moshi.ToJson; import com.squareup.moshi.ToJson;
public final class FromJsonWithoutStrings { public final class FromJsonWithoutStrings {
public void run() throws Exception { public void run() throws Exception {
// for some reason our JSON has date and time as separate fields - // For some reason our JSON has date and time as separate fields. We will clean that up during
// we will clean that up during parsing: Moshi will first parse // parsing: Moshi will first parse the JSON directly to an EventJson and from that the
// the JSON directly to an EventJson and from that the EventJsonAdapter // EventJsonAdapter will create the actual Event.
// will create the actual Event
String json = "" String json = ""
+ "{\n" + "{\n"
+ " \"title\": \"Blackjack tournament\",\n" + " \"title\": \"Blackjack tournament\",\n"
@@ -34,9 +32,7 @@ public final class FromJsonWithoutStrings {
+ " \"begin_time\": \"17:04\"\n" + " \"begin_time\": \"17:04\"\n"
+ "}\n"; + "}\n";
Moshi moshi = new Moshi.Builder() Moshi moshi = new Moshi.Builder().add(new EventJsonAdapter()).build();
.add(new EventJsonAdapter())
.build();
JsonAdapter<Event> jsonAdapter = moshi.adapter(Event.class); JsonAdapter<Event> jsonAdapter = moshi.adapter(Event.class);
Event event = jsonAdapter.fromJson(json); Event event = jsonAdapter.fromJson(json);
@@ -58,8 +54,7 @@ public final class FromJsonWithoutStrings {
String title; String title;
String beginDateAndTime; String beginDateAndTime;
@Override @Override public String toString() {
public String toString() {
return "Event{" + return "Event{" +
"title='" + title + '\'' + "title='" + title + '\'' +
", beginDateAndTime='" + beginDateAndTime + '\'' + ", beginDateAndTime='" + beginDateAndTime + '\'' +
@@ -68,23 +63,19 @@ public final class FromJsonWithoutStrings {
} }
private static final class EventJsonAdapter { private static final class EventJsonAdapter {
@FromJson Event eventFromJson(EventJson eventJson) {
@FromJson
Event eventFromJson(EventJson eventJson) {
Event event = new Event(); Event event = new Event();
event.title = eventJson.title; event.title = eventJson.title;
event.beginDateAndTime = eventJson.begin_date + " " + eventJson.begin_time; event.beginDateAndTime = eventJson.begin_date + " " + eventJson.begin_time;
return event; return event;
} }
@ToJson @ToJson EventJson eventToJson(Event event) {
EventJson eventToJson(Event event) {
EventJson json = new EventJson(); EventJson json = new EventJson();
json.title = event.title; json.title = event.title;
json.begin_date = event.beginDateAndTime.substring(0, 8); json.begin_date = event.beginDateAndTime.substring(0, 8);
json.begin_time = event.beginDateAndTime.substring(9, 14); json.begin_time = event.beginDateAndTime.substring(9, 14);
return json; return json;
} }
} }
} }

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2016 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.moshi.recipes.models;
import com.squareup.moshi.Json;
public final class Player {
public final String username;
public final @Json(name = "lucky number") int luckyNumber;
public Player(String username, int luckyNumber) {
this.username = username;
this.luckyNumber = luckyNumber;
}
@Override public String toString() {
return username + " gets lucky with " + luckyNumber;
}
}