1 /*
2  * Copyright (C) 2015 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package com.squareup.moshi;
17
18 import java.io.IOException;
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.Type;
21 import java.util.Map;
22 import java.util.Set;
23 import javax.annotation.Nullable;
24
25 /**
26  * Converts maps with string keys to JSON objects.
27  *
28  * TODO: support maps with other key types and convert to/from strings.
29  */

30 final class MapJsonAdapter<K, V> extends JsonAdapter<Map<K, V>> {
31   public static final Factory FACTORY = new Factory() {
32     @Override public @Nullable JsonAdapter<?> create(
33         Type type, Set<? extends Annotation> annotations, Moshi moshi) {
34       if (!annotations.isEmpty()) return null;
35       Class<?> rawType = Types.getRawType(type);
36       if (rawType != Map.classreturn null;
37       Type[] keyAndValue = Types.mapKeyAndValueTypes(type, rawType);
38       return new MapJsonAdapter<>(moshi, keyAndValue[0], keyAndValue[1]).nullSafe();
39     }
40   };
41
42   private final JsonAdapter<K> keyAdapter;
43   private final JsonAdapter<V> valueAdapter;
44
45   MapJsonAdapter(Moshi moshi, Type keyType, Type valueType) {
46     this.keyAdapter = moshi.adapter(keyType);
47     this.valueAdapter = moshi.adapter(valueType);
48   }
49
50   @Override public void toJson(JsonWriter writer, Map<K, V> map) throws IOException {
51     writer.beginObject();
52     for (Map.Entry<K, V> entry : map.entrySet()) {
53       if (entry.getKey() == null) {
54         throw new JsonDataException("Map key is null at " + writer.getPath());
55       }
56       writer.promoteValueToName();
57       keyAdapter.toJson(writer, entry.getKey());
58       valueAdapter.toJson(writer, entry.getValue());
59     }
60     writer.endObject();
61   }
62
63   @Override public Map<K, V> fromJson(JsonReader reader) throws IOException {
64     LinkedHashTreeMap<K, V> result = new LinkedHashTreeMap<>();
65     reader.beginObject();
66     while (reader.hasNext()) {
67       reader.promoteNameToValue();
68       K name = keyAdapter.fromJson(reader);
69       V value = valueAdapter.fromJson(reader);
70       V replaced = result.put(name, value);
71       if (replaced != null) {
72         throw new JsonDataException("Map key '" + name + "' has multiple values at path "
73             + reader.getPath() + ": " + replaced + " and " + value);
74       }
75     }
76     reader.endObject();
77     return result;
78   }
79
80   @Override public String toString() {
81     return "JsonAdapter(" + keyAdapter + "=" + valueAdapter + ")";
82   }
83 }
84