• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.fasterxml.jackson.databind.ser;
2 
3 import java.io.*;
4 import java.util.*;
5 import java.util.concurrent.ConcurrentHashMap;
6 import java.util.concurrent.ConcurrentSkipListMap;
7 
8 import com.fasterxml.jackson.annotation.*;
9 import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
10 import com.fasterxml.jackson.core.*;
11 import com.fasterxml.jackson.databind.*;
12 import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
13 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
14 import com.fasterxml.jackson.databind.testutil.NoCheckSubTypeValidator;
15 
16 @SuppressWarnings("serial")
17 public class TestMapSerialization extends BaseMapTest
18 {
19     @JsonSerialize(using=PseudoMapSerializer.class)
20     static class PseudoMap extends LinkedHashMap<String,String>
21     {
PseudoMap(String... values)22         public PseudoMap(String... values) {
23             for (int i = 0, len = values.length; i < len; i += 2) {
24                 put(values[i], values[i+1]);
25             }
26         }
27     }
28 
29     static class PseudoMapSerializer extends JsonSerializer<Map<String,String>>
30     {
31         @Override
serialize(Map<String,String> value, JsonGenerator gen, SerializerProvider provider)32         public void serialize(Map<String,String> value,
33                 JsonGenerator gen, SerializerProvider provider) throws IOException
34         {
35             // just use standard Map.toString(), output as JSON String
36             gen.writeString(value.toString());
37         }
38     }
39 
40     // [databind#335]
41     static class MapOrderingBean {
42         @JsonPropertyOrder(alphabetic=true)
43         public LinkedHashMap<String,Integer> map;
44 
MapOrderingBean(String... keys)45         public MapOrderingBean(String... keys) {
46             map = new LinkedHashMap<String,Integer>();
47             int ix = 1;
48             for (String key : keys) {
49                 map.put(key, ix++);
50             }
51         }
52     }
53 
54     // [databind#565]: Support ser/deser of Map.Entry
55     static class StringIntMapEntry implements Map.Entry<String,Integer> {
56         public final String k;
57         public final Integer v;
StringIntMapEntry(String k, Integer v)58         public StringIntMapEntry(String k, Integer v) {
59             this.k = k;
60             this.v = v;
61         }
62 
63         @Override
getKey()64         public String getKey() {
65             return k;
66         }
67 
68         @Override
getValue()69         public Integer getValue() {
70             return v;
71         }
72 
73         @Override
setValue(Integer value)74         public Integer setValue(Integer value) {
75             throw new UnsupportedOperationException();
76         }
77     }
78 
79     static class StringIntMapEntryWrapper {
80         public StringIntMapEntry value;
81 
StringIntMapEntryWrapper(String k, Integer v)82         public StringIntMapEntryWrapper(String k, Integer v) {
83             value = new StringIntMapEntry(k, v);
84         }
85     }
86 
87     // for [databind#691]
88     @JsonTypeInfo(use=JsonTypeInfo.Id.NAME)
89     @JsonTypeName("mymap")
90     static class MapWithTypedValues extends LinkedHashMap<String,String> { }
91 
92     @JsonTypeInfo(use = Id.CLASS)
93     public static class Mixin691 { }
94 
95     /*
96     /**********************************************************
97     /* Test methods
98     /**********************************************************
99      */
100 
101     final private ObjectMapper MAPPER = objectMapper();
102 
testUsingObjectWriter()103     public void testUsingObjectWriter() throws IOException
104     {
105         ObjectWriter w = MAPPER.writerFor(Object.class);
106         Map<String,Object> map = new LinkedHashMap<String,Object>();
107         map.put("a", 1);
108         String json = w.writeValueAsString(map);
109         assertEquals(aposToQuotes("{'a':1}"), json);
110     }
111 
testMapSerializer()112     public void testMapSerializer() throws IOException
113     {
114         assertEquals("\"{a=b, c=d}\"", MAPPER.writeValueAsString(new PseudoMap("a", "b", "c", "d")));
115     }
116 
117     // problems with map entries, values
testMapKeySetValuesSerialization()118     public void testMapKeySetValuesSerialization() throws IOException
119     {
120         Map<String,String> map = new HashMap<String,String>();
121         map.put("a", "b");
122         assertEquals("[\"a\"]", MAPPER.writeValueAsString(map.keySet()));
123         assertEquals("[\"b\"]", MAPPER.writeValueAsString(map.values()));
124 
125         // TreeMap has similar inner class(es):
126         map = new TreeMap<String,String>();
127         map.put("c", "d");
128         assertEquals("[\"c\"]", MAPPER.writeValueAsString(map.keySet()));
129         assertEquals("[\"d\"]", MAPPER.writeValueAsString(map.values()));
130 
131         // and for [JACKSON-533], same for concurrent maps
132         map = new ConcurrentHashMap<String,String>();
133         map.put("e", "f");
134         assertEquals("[\"e\"]", MAPPER.writeValueAsString(map.keySet()));
135         assertEquals("[\"f\"]", MAPPER.writeValueAsString(map.values()));
136     }
137 
138     // sort Map entries by key
testOrderByKey()139     public void testOrderByKey() throws IOException
140     {
141         ObjectMapper m = new ObjectMapper();
142         assertFalse(m.isEnabled(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS));
143         LinkedHashMap<String,Integer> map = new LinkedHashMap<String,Integer>();
144         map.put("b", 3);
145         map.put("a", 6);
146         // by default, no (re)ordering:
147         assertEquals("{\"b\":3,\"a\":6}", m.writeValueAsString(map));
148         // but can be changed
149         ObjectWriter sortingW =  m.writer(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
150         assertEquals("{\"a\":6,\"b\":3}", sortingW.writeValueAsString(map));
151     }
152 
153     // related to [databind#1411]
testOrderByWithNulls()154     public void testOrderByWithNulls() throws IOException
155     {
156         ObjectWriter sortingW = MAPPER.writer(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
157         // 16-Oct-2016, tatu: but mind the null key, if any
158         Map<String,Integer> mapWithNullKey = new LinkedHashMap<String,Integer>();
159         mapWithNullKey.put(null, 1);
160         mapWithNullKey.put("b", 2);
161         // 16-Oct-2016, tatu: By default, null keys are not accepted...
162         try {
163             /*String json =*/ sortingW.writeValueAsString(mapWithNullKey);
164             //assertEquals(aposToQuotes("{'':1,'b':2}"), json);
165         } catch (JsonMappingException e) {
166             verifyException(e, "Null key for a Map not allowed");
167         }
168     }
169 
170     // [Databind#335]
testOrderByKeyViaProperty()171     public void testOrderByKeyViaProperty() throws IOException
172     {
173         MapOrderingBean input = new MapOrderingBean("c", "b", "a");
174         String json = MAPPER.writeValueAsString(input);
175         assertEquals(aposToQuotes("{'map':{'a':3,'b':2,'c':1}}"), json);
176     }
177 
178     // [Databind#565]
testMapEntry()179     public void testMapEntry() throws IOException
180     {
181         StringIntMapEntry input = new StringIntMapEntry("answer", 42);
182         String json = MAPPER.writeValueAsString(input);
183         assertEquals(aposToQuotes("{'answer':42}"), json);
184 
185         StringIntMapEntry[] array = new StringIntMapEntry[] { input };
186         json = MAPPER.writeValueAsString(array);
187         assertEquals(aposToQuotes("[{'answer':42}]"), json);
188 
189         // and maybe with bit of extra typing?
190         ObjectMapper mapper = new ObjectMapper().activateDefaultTyping(NoCheckSubTypeValidator.instance,
191                 DefaultTyping.NON_FINAL);
192         json = mapper.writeValueAsString(input);
193         assertEquals(aposToQuotes("['"+StringIntMapEntry.class.getName()+"',{'answer':42}]"),
194                 json);
195     }
196 
testMapEntryWrapper()197     public void testMapEntryWrapper() throws IOException
198     {
199         StringIntMapEntryWrapper input = new StringIntMapEntryWrapper("answer", 42);
200         String json = MAPPER.writeValueAsString(input);
201         assertEquals(aposToQuotes("{'value':{'answer':42}}"), json);
202     }
203 
204     // [databind#691]
testNullJsonMapping691()205     public void testNullJsonMapping691() throws Exception
206     {
207         MapWithTypedValues input = new MapWithTypedValues();
208         input.put("id", "Test");
209         input.put("NULL", null);
210 
211         String json = MAPPER.writeValueAsString(input);
212 
213         assertEquals(aposToQuotes("{'@type':'mymap','id':'Test','NULL':null}"),
214                 json);
215     }
216 
217     // [databind#691]
testNullJsonInTypedMap691()218     public void testNullJsonInTypedMap691() throws Exception {
219         Map<String, String> map = new HashMap<String, String>();
220         map.put("NULL", null);
221 
222         ObjectMapper mapper = new ObjectMapper();
223         mapper.addMixIn(Object.class, Mixin691.class);
224         String json = mapper.writeValueAsString(map);
225         assertEquals("{\"@class\":\"java.util.HashMap\",\"NULL\":null}", json);
226     }
227 
228     // [databind#1513]
testConcurrentMaps()229     public void testConcurrentMaps() throws Exception
230     {
231         final ObjectWriter w = MAPPER.writer(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
232 
233         Map<String,String> input = new ConcurrentSkipListMap<String,String>();
234         input.put("x", "y");
235         input.put("a", "b");
236         String json = w.writeValueAsString(input);
237         assertEquals(aposToQuotes("{'a':'b','x':'y'}"), json);
238 
239         input = new ConcurrentHashMap<String,String>();
240         input.put("x", "y");
241         input.put("a", "b");
242         json = w.writeValueAsString(input);
243         assertEquals(aposToQuotes("{'a':'b','x':'y'}"), json);
244 
245         // One more: while not technically concurrent map at all, exhibits same issue
246         input = new Hashtable<String,String>();
247         input.put("x", "y");
248         input.put("a", "b");
249         json = w.writeValueAsString(input);
250         assertEquals(aposToQuotes("{'a':'b','x':'y'}"), json);
251     }
252 }
253