Document @Json and @JsonQualifier in the README.

This commit is contained in:
jwilson
2016-01-11 00:24:00 -08:00
parent 7d0e2f537b
commit d8820b03f4
5 changed files with 332 additions and 95 deletions

171
README.md
View File

@@ -134,13 +134,13 @@ Voila:
#### Another example
Note that the method annotated with `@FromJson` does not need to take a String as an argument. Rather it can take
input of any type and Moshi will first parse the JSON to an object of that type and then use the `@FromJson`
method to produce the desired final value. Conversely, the method annotated with `@ToJson` does not have to produce
a String.
Note that the method annotated with `@FromJson` does not need to take a String as an argument.
Rather it can take input of any type and Moshi will first parse the JSON to an object of that type
and then use the `@FromJson` method to produce the desired final value. Conversely, the method
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
separate strings.
Assume, for example, that we have to parse a JSON in which the date and time of an event are
represented as two separate strings.
```json
{
@@ -151,51 +151,50 @@ separate strings.
```
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
want Moshi to produce like this:
later point. Also, we would like to have all variable names in CamelCase. Therefore, the `Event`
class we want Moshi to produce like this:
```java
class Event {
String title;
String beginDateAndTime;
String title;
String beginDateAndTime;
}
```
Instead of manually parsing the JSON line per line (which we could also do) we can have Moshi
do the transformation automatically. We simply define another class `EventJson` that directly corresponds to the JSON structure:
Instead of manually parsing the JSON line per line (which we could also do) we can have Moshi do the
transformation automatically. We simply define another class `EventJson` that directly corresponds
to the JSON structure:
```java
class EventJson {
String title;
String begin_date;
String begin_time;
String title;
String begin_date;
String begin_time;
}
```
And another class with the appropriate `@FromJson` and `@ToJson` methods that are telling Moshi how to convert
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
create an `EventJson` object and then serialize that object as usual.
And another class with the appropriate `@FromJson` and `@ToJson` methods that are telling Moshi how
to convert 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 create an `EventJson` object and then serialize that object as
usual.
```java
class EventJsonAdapter {
@FromJson Event eventFromJson(EventJson eventJson) {
Event event = new Event();
event.title = eventJson.title;
event.beginDateAndTime = eventJson.begin_date + " " + eventJson.begin_time;
return event;
}
@FromJson
Event eventFromJson(EventJson eventJson) {
Event event = new Event();
event.title = eventJson.title;
event.beginDateAndTime = eventJson.begin_date + " " + eventJson.begin_time;
return event;
}
@ToJson
EventJson eventToJson(Event event) {
EventJson json = new EventJson();
json.title = event.title;
json.begin_date = event.beginDateAndTime.substring(0, 8);
json.begin_time = event.beginDateAndTime.substring(9, 14);
return json;
}
@ToJson EventJson eventToJson(Event event) {
EventJson json = new EventJson();
json.title = event.title;
json.begin_date = event.beginDateAndTime.substring(0, 8);
json.begin_time = event.beginDateAndTime.substring(9, 14);
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
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
--------

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