001package squidpony.store.json;
002
003import com.badlogic.gdx.utils.Json;
004import com.badlogic.gdx.utils.JsonValue;
005import com.badlogic.gdx.utils.JsonWriter;
006import com.badlogic.gdx.utils.reflect.ClassReflection;
007import com.badlogic.gdx.utils.reflect.ReflectionException;
008import regexodus.Pattern;
009import squidpony.FakeLanguageGen;
010import squidpony.Maker;
011import squidpony.StringStringMap;
012import squidpony.squidgrid.Direction;
013import squidpony.squidgrid.Radius;
014import squidpony.squidmath.*;
015
016import java.util.*;
017
018/**
019 * Augmented version of LibGDX's Json class that knows how to handle various data types common in SquidLib.
020 * This includes OrderedMap, which notably allows non-String keys (LibGDX's default Map serializer requires keys to be
021 * Strings), but does not currently allow the IHasher to be set (which only should affect OrderedMaps with array keys).
022 * It also makes significantly shorter serialized output for 2D char arrays, GreasedRegion and FakeLanguageGen objects,
023 * and various collections (IntDoubleOrderedMap, IntVLA, Arrangement, K2, and K2V1 at least).
024 * Created by Tommy Ettinger on 1/9/2017.
025 */
026public class JsonConverter extends Json {
027    /**
028     * Creates a new JsonConverter using "minimal" output type, so it omits double quotes whenever possible but gives
029     * up compatibility with most other JSON readers. Give the constructor
030     * {@link JsonWriter.OutputType#json} if you need full compatibility.
031     */
032    public JsonConverter() {
033        super();
034        initialize(this);
035    }
036
037    /**
038     * Creates a new JsonConverter with the given OutputType; typically minimal is fine, but compatibility may require
039     * you to use json; the javascript type is a sort of middle ground.
040     * @param outputType a JsonWriter.OutputType enum value that determines what syntax can be omitted from the output
041     */
042    public JsonConverter(JsonWriter.OutputType outputType) {
043        super(outputType);
044        initialize(this);
045    }
046
047    public static final Object INVALID = Float.NaN;
048
049    public static void initialize(Json json)
050    {
051        json.addClassTag("#St", String.class);
052        json.addClassTag("#Z", Boolean.class);
053        json.addClassTag("#z", boolean.class);
054        json.addClassTag("#B", Byte.class);
055        json.addClassTag("#b", byte.class);
056        json.addClassTag("#S", Short.class);
057        json.addClassTag("#s", short.class);
058        json.addClassTag("#C", Character.class);
059        json.addClassTag("#c", char.class);
060        json.addClassTag("#I", Integer.class);
061        json.addClassTag("#i", int.class);
062        json.addClassTag("#F", Float.class);
063        json.addClassTag("#f", float.class);
064        json.addClassTag("#L", Long.class);
065        json.addClassTag("#l", long.class);
066        json.addClassTag("#D", Double.class);
067        json.addClassTag("#d", double.class);
068        json.addClassTag("#Co", Coord.class);
069        json.addClassTag("#Co3", Coord3D.class);
070        json.addClassTag("#CoD", CoordDouble.class);
071        json.addClassTag("#SSet", SortedSet.class);
072        json.addClassTag("#Patt", Pattern.class);
073        json.addClassTag("#Grea", GreasedRegion.class);
074        json.addClassTag("#IDOM", IntDoubleOrderedMap.class);
075        json.addClassTag("#Lang", FakeLanguageGen.class);
076        json.addClassTag("#LnAl", FakeLanguageGen.Alteration.class);
077        json.addClassTag("#LnMd", FakeLanguageGen.Modifier.class);
078        json.addClassTag("#SSMp", StringStringMap.class);
079        json.addClassTag("#OMap", OrderedMap.class);
080        json.addClassTag("#EOMp", EnumOrderedMap.class);
081        json.addClassTag("#OSet", OrderedSet.class);
082        json.addClassTag("#UOSt", UnorderedSet.class);
083        json.addClassTag("#EOSt", EnumOrderedSet.class);
084        json.addClassTag("#Aran", Arrangement.class);
085        json.addClassTag("#K2", K2.class);
086        json.addClassTag("#K2V1", K2V1.class);
087        json.addClassTag("#IVLA", IntVLA.class);
088        json.addClassTag("#SVLA", ShortVLA.class);
089        json.addClassTag("#FNoi", FastNoise.class);
090        json.addClassTag("#RNG", RNG.class);
091        json.addClassTag("#SRNG", StatefulRNG.class);
092        json.addClassTag("#EdiR", EditRNG.class);
093        json.addClassTag("#CriR", CriticalRNG.class);
094        json.addClassTag("#DhaR", DharmaRNG.class);
095        json.addClassTag("#DecR", DeckRNG.class);
096        json.addClassTag("#Ligh", LightRNG.class);
097        json.addClassTag("#Linn", LinnormRNG.class);
098        json.addClassTag("#DivR", DiverRNG.class);
099        json.addClassTag("#LonP", LongPeriodRNG.class);
100        json.addClassTag("#MnwR", MoonwalkRNG.class);
101        json.addClassTag("#ThrA", ThrustAltRNG.class);
102        json.addClassTag("#SlkR", SilkRNG.class);
103        json.addClassTag("#LthR", Lathe32RNG.class);
104        json.addClassTag("#SblR", SobolQRNG.class);
105        json.addClassTag("#GWTR", GWTRNG.class);
106        json.addClassTag("#XoRo", XoRoRNG.class);
107        json.addClassTag("#MMvR", MiniMover64RNG.class);
108        json.addClassTag("#Mist", CrossHash.Mist.class);
109        json.addClassTag("#Yolk", CrossHash.Yolk.class);
110        json.addClassTag("#Dir", Direction.class);
111        json.addClassTag("#Rad", Radius.class);
112
113        json.setSerializer(Pattern.class, new Serializer<Pattern>() {
114            @Override
115            public void write(Json json, Pattern object, Class knownType) {
116                if(object == null)
117                {
118                    json.writeValue(null);
119                    return;
120                }
121                json.writeValue((Object) object.serializeToString(), String.class);
122            }
123
124            @Override
125            public Pattern read(Json json, JsonValue jsonData, Class type) {
126                if(jsonData == null || jsonData.isNull()) return null;
127                String data = jsonData.asString();
128                if(data == null || data.length() < 2) return null;
129                return Pattern.deserializeFromString(data);
130            }
131        });
132        json.setSerializer(Coord.class, new Serializer<Coord>() {
133            @Override
134            public void write(Json json, Coord object, Class knownType) {
135                if(object == null)
136                {
137                    json.writeValue(null);
138                    return;
139                }
140                json.writeArrayStart();
141                json.writeValue(object.x, int.class);
142                json.writeValue(object.y, int.class);
143                json.writeArrayEnd();
144            }
145
146            @Override
147            public Coord read(Json json, JsonValue jsonData, Class type) {
148                if(jsonData == null || jsonData.isNull()) return null;
149                return Coord.get(jsonData.getInt(0), jsonData.getInt(1));
150            }
151        });
152
153        json.setSerializer(GreasedRegion.class, new Serializer<GreasedRegion>() {
154            @Override
155            public void write(Json json, GreasedRegion object, Class knownType) {
156                if(object == null)
157                {
158                    json.writeValue(null);
159                    return;
160                }
161                json.writeObjectStart();
162                json.writeObjectStart("items", GreasedRegion.class, GreasedRegion.class);
163                json.writeValue("w", object.width);
164                json.writeValue("h", object.height);
165                json.writeValue("d", object.data);
166                json.writeObjectEnd();
167                json.writeObjectEnd();
168            }
169
170            @Override
171            public GreasedRegion read(Json json, JsonValue jsonData, Class type) {
172                if(jsonData == null || jsonData.isNull()) return null;
173                return new GreasedRegion(jsonData.get("d").asLongArray(), jsonData.getInt("w"), jsonData.getInt("h"));
174            }
175        });
176        json.setSerializer(IntVLA.class, new Serializer<IntVLA>() {
177            @Override
178            public void write(Json json, IntVLA object, Class knownType) {
179                if(object == null)
180                {
181                    json.writeValue(null);
182                    return;
183                }
184                json.writeValue(object.toArray(), int[].class);
185            }
186
187            @Override
188            public IntVLA read(Json json, JsonValue jsonData, Class type) {
189                if(jsonData == null || jsonData.isNull()) return null;
190                return new IntVLA(jsonData.asIntArray());
191            }
192        });
193
194        json.setSerializer(IntDoubleOrderedMap.class, new Serializer<IntDoubleOrderedMap>() {
195            @Override
196            public void write(Json json, IntDoubleOrderedMap object, Class knownType) {
197                if(object == null)
198                {
199                    json.writeValue(null);
200                    return;
201                }
202                json.writeObjectStart();
203                json.writeArrayStart("k");
204                for (int i = 0; i < object.size(); i++) {
205                    json.writeValue(object.keyAt(i));
206                }
207                json.writeArrayEnd();
208                json.writeArrayStart("v");
209                for (int i = 0; i < object.size(); i++) {
210                    json.writeValue(object.getAt(i));
211                }
212                json.writeArrayEnd();
213                json.writeValue("f", object.f);
214                json.writeObjectEnd();
215            }
216
217            @Override
218            public IntDoubleOrderedMap read(Json json, JsonValue jsonData, Class type) {
219                if(jsonData == null || jsonData.isNull()) return null;
220                return new IntDoubleOrderedMap(jsonData.get("k").asIntArray(), jsonData.get("v").asDoubleArray(), jsonData.getFloat("f"));
221            }
222        });
223
224        json.setSerializer(StringStringMap.class, new Serializer<StringStringMap>() {
225            @Override
226            public void write(Json json, StringStringMap object, Class knownType) {
227                if(object == null)
228                {
229                    json.writeValue(null);
230                    return;
231                }
232                json.writeObjectStart();
233                json.writeValue("k", object.keysAsOrderedSet(), OrderedSet.class, String.class);
234                json.writeValue("v", object.valuesAsList(), ArrayList.class, String.class);
235                json.writeValue("f", object.f);
236                json.writeObjectEnd();
237            }
238
239            @Override
240            @SuppressWarnings("unchecked")
241            public StringStringMap read(Json json, JsonValue jsonData, Class type) {
242                if(jsonData == null || jsonData.isNull()) return null;
243                return new StringStringMap(json.readValue(OrderedSet.class, String.class, jsonData.get("k")),
244                        json.readValue(ArrayList.class, String.class, jsonData.get("v")), jsonData.getFloat("f"));
245            }
246        });
247
248        json.setSerializer(OrderedMap.class, new Serializer<OrderedMap>() {
249            @Override
250            public void write(Json json, OrderedMap object, Class knownType) {
251                if(object == null)
252                {
253                    json.writeValue(null);
254                    return;
255                }
256                json.writeObjectStart();
257                json.writeValue("f", object.f);
258                if(!object.isEmpty()) {
259                    json.writeValue("k", object.firstKey(), null);
260                    json.writeValue("v", object.getAt(0), null);
261                    int sz = object.size();
262                    Object[] r = new Object[(sz - 1) * 2];
263                    for (int i = 1, p = 0; i < sz; i++) {
264                        r[p++] = object.keyAt(i);
265                        r[p++] = object.getAt(i);
266                    }
267                    json.writeValue("r", r, Object[].class, Object.class);
268                }
269                json.writeObjectEnd();
270            }
271
272            @Override
273                        public OrderedMap read(Json json, JsonValue jsonData, Class type) {
274                if(jsonData == null || jsonData.isNull()) return null;
275                float f = json.readValue("f", float.class, jsonData);
276                Object k = json.readValue("k", null, INVALID, jsonData);
277                Object v = json.readValue("v", null, INVALID, jsonData);
278                Object[] r = json.readValue("r", Object[].class, jsonData);
279                if(k == INVALID)
280                    return new OrderedMap(0, f);
281                return Maker.makeOM(f, k, v, r);
282                //return new OrderedMap(json.readValue(OrderedSet.class, jsonData.get("k")),
283                //        json.readValue(ArrayList.class, jsonData.get("v")), jsonData.getFloat("f"));
284            }
285        });
286        json.setSerializer(EnumOrderedMap.class, new Serializer<EnumOrderedMap>() {
287            @Override
288            public void write(Json json, EnumOrderedMap object, Class knownType) {
289                if(object == null)
290                {
291                    json.writeValue(null);
292                    return;
293                }
294                json.writeObjectStart();
295                if(!object.isEmpty()) {
296                    json.writeValue("c", object.firstKey().getClass().getName());
297                    json.writeValue("k", object.firstKey(), null);
298                    json.writeValue("v", object.getAt(0), null);
299                    int sz = object.size();
300                    Object[] r = new Object[(sz - 1) * 2];
301                    for (int i = 1, p = 0; i < sz; i++) {
302                        r[p++] = object.keyAt(i);
303                        r[p++] = object.getAt(i);
304                    }
305                    json.writeValue("r", r, Object[].class, Object.class);
306                }
307                else
308                    json.writeValue("c", "default");
309                json.writeObjectEnd();
310            }
311
312            @Override
313            @SuppressWarnings("unchecked")
314            public EnumOrderedMap read(Json json, JsonValue jsonData, Class type) {
315                if(jsonData == null || jsonData.isNull()) return null;
316                String c = json.readValue("c", String.class, "", jsonData);
317                if("default".equals(c))
318                    return new EnumOrderedMap();
319                try {
320                    Class<? extends Enum<?>> cl = ClassReflection.forName(c);
321                    if(!ClassReflection.isEnum(cl))
322                        return null;
323                    Enum<?> k = json.readValue("k", cl, jsonData);
324                    Object v = json.readValue("v", null, INVALID, jsonData);
325                    Object[] r = json.readValue("r", Object[].class, jsonData);
326                    if(v == INVALID)
327                        return new EnumOrderedMap();
328                    return Maker.makeEOM(k, v, r);
329                } catch (ReflectionException e) {
330                    return null;
331                }
332                //return new OrderedMap(json.readValue(OrderedSet.class, jsonData.get("k")),
333                //        json.readValue(ArrayList.class, jsonData.get("v")), jsonData.getFloat("f"));
334            }
335        });
336        json.setSerializer(EnumOrderedSet.class, new Serializer<EnumOrderedSet>() {
337            @Override
338            public void write(Json json, EnumOrderedSet object, Class knownType) {
339                if(object == null)
340                {
341                    json.writeValue(null);
342                    return;
343                }
344                json.writeObjectStart();
345                if(!object.isEmpty()) {
346                    json.writeValue("c", object.first().getClass().getName());
347                    json.writeValue("i", object.first(), null);
348                    int sz = object.size();
349                    Object[] r = new Object[sz - 1];
350                    for (int i = 1, p = 0; i < sz; i++) {
351                        r[p++] = object.getAt(i);
352                    }
353                    json.writeValue("r", r, Object[].class, Object.class);
354                }
355                else
356                    json.writeValue("c", "default");
357                json.writeObjectEnd();
358            }
359
360            @Override
361            @SuppressWarnings("unchecked")
362            public EnumOrderedSet read(Json json, JsonValue jsonData, Class type) {
363                if(jsonData == null || jsonData.isNull()) return null;
364                String c = json.readValue("c", String.class, "", jsonData);
365                if("default".equals(c))
366                    return new EnumOrderedSet();
367                try {
368                    Class<? extends Enum<?>> cl = ClassReflection.forName(c);
369                    if(!ClassReflection.isEnum(cl))
370                        return null;
371                    Enum<?> i = json.readValue("i", cl, jsonData);
372                    Object[] r = json.readValue("r", Object[].class, jsonData);
373                    return Maker.makeEOS(i, (Enum<?>[]) r);
374                } catch (ReflectionException e) {
375                    return null;
376                }
377                //return new OrderedMap(json.readValue(OrderedSet.class, jsonData.get("k")),
378                //        json.readValue(ArrayList.class, jsonData.get("v")), jsonData.getFloat("f"));
379            }
380        });
381
382        json.setSerializer(EnumMap.class, new Serializer<EnumMap>() {
383            @Override
384            public void write(Json json, EnumMap object, Class knownType) {
385                if(object == null)
386                {
387                    json.writeValue(null);
388                    return;
389                }
390                json.writeObjectStart();
391                if(!object.isEmpty()) {
392                    Iterator it = object.entrySet().iterator();
393                    Map.Entry en = (Map.Entry)it.next();
394                    json.writeValue("e", en.getKey(), Enum.class);
395                    json.writeValue("v", en.getValue(), null);
396                    int sz = object.size();
397                    Object[] r = new Object[(sz - 1) * 2];
398                    for (int i = 1, p = 0; i < sz; i++) {
399                        if(!it.hasNext())
400                            break;
401                        en = (Map.Entry)it.next();
402                        r[p++] = en.getKey();
403                        r[p++] = en.getValue();
404                    }
405                    json.writeValue("r", r, Object[].class, Object.class);
406                }
407                json.writeObjectEnd();
408            }
409
410            @Override
411            @SuppressWarnings("unchecked")
412            public EnumMap read(Json json, JsonValue jsonData, Class type) {
413                if(jsonData == null || jsonData.isNull() || jsonData.size == 0) return null;
414                return new EnumMap(Maker.makeOM(0.75f,
415                        json.readValue("e", null, jsonData),
416                        json.readValue("v", null, jsonData),
417                        json.readValue("r", Object[].class, jsonData)));
418                //return new OrderedMap(json.readValue(OrderedSet.class, jsonData.get("k")),
419                //        json.readValue(ArrayList.class, jsonData.get("v")), jsonData.getFloat("f"));
420            }
421        });
422        json.setSerializer(Arrangement.class, new Serializer<Arrangement>() {
423            @Override
424            public void write(Json json, Arrangement object, Class knownType) {
425                if(object == null)
426                {
427                    json.writeValue(null);
428                    return;
429                }
430                json.writeObjectStart();
431                json.writeValue("k", object.keysAsOrderedSet(), OrderedSet.class);
432                json.writeValue("f", object.f);
433                json.writeObjectEnd();
434            }
435
436            @Override
437            @SuppressWarnings("unchecked")
438            public Arrangement read(Json json, JsonValue jsonData, Class type) {
439                if(jsonData == null || jsonData.isNull()) return null;
440                return new Arrangement(json.readValue(OrderedSet.class, jsonData.get("k")), jsonData.getFloat("f"));
441            }
442        });
443
444        json.setSerializer(K2.class, new Serializer<K2>() {
445            @Override
446            public void write(Json json, K2 object, Class knownType) {
447                if(object == null)
448                {
449                    json.writeValue(null);
450                    return;
451                }
452                json.writeObjectStart();
453                json.writeValue("a", object.getSetA(), SortedSet.class);
454                json.writeValue("b", object.getSetB(), SortedSet.class);
455                json.writeObjectEnd();
456            }
457
458            @Override
459            @SuppressWarnings("unchecked")
460            public K2 read(Json json, JsonValue jsonData, Class type) {
461                if(jsonData == null || jsonData.isNull()) return null;
462                return new K2(json.readValue(SortedSet.class, jsonData.get("a")), json.readValue(SortedSet.class, jsonData.get("b")));
463            }
464        });
465
466        json.setSerializer(K2V1.class, new Serializer<K2V1>() {
467            @Override
468            public void write(Json json, K2V1 object, Class knownType) {
469                if(object == null)
470                {
471                    json.writeValue(null);
472                    return;
473                }
474                json.writeObjectStart();
475                json.writeValue("a", object.getSetA(), SortedSet.class);
476                json.writeValue("b", object.getSetB(), SortedSet.class);
477                json.writeValue("q", object.getListQ(), ArrayList.class);
478                json.writeObjectEnd();
479            }
480
481            @Override
482            @SuppressWarnings("unchecked")
483            public K2V1 read(Json json, JsonValue jsonData, Class type) {
484                if(jsonData == null || jsonData.isNull()) return null;
485                return new K2V1(
486                        json.readValue(SortedSet.class, jsonData.get("a")),
487                        json.readValue(SortedSet.class, jsonData.get("b")),
488                        json.readValue(ArrayList.class, jsonData.get("q")));
489            }
490        });
491
492        json.setSerializer(char[][].class, new Serializer<char[][]>() {
493            @Override
494            public void write(Json json, char[][] object, Class knownType) {
495                if(object == null)
496                {
497                    json.writeValue(null);
498                    return;
499                }
500                int sz = object.length;
501                json.writeArrayStart();
502                for (int i = 0; i < sz; i++) {
503                    json.writeValue(String.valueOf(object[i]));
504                }
505                json.writeArrayEnd();
506            }
507
508            @Override
509            public char[][] read(Json json, JsonValue jsonData, Class type) {
510                if(jsonData == null || jsonData.isNull())
511                    return null;
512                int sz = jsonData.size;
513                char[][] data = new char[sz][];
514                JsonValue c = jsonData.child();
515                for (int i = 0; i < sz && c != null; i++, c = c.next()) {
516                    data[i] = c.asString().toCharArray();
517                }
518                return data;
519            }
520        });
521        json.setSerializer(FakeLanguageGen.class, new Serializer<FakeLanguageGen>() {
522            @Override
523            public void write(Json json, FakeLanguageGen object, Class knownType) {
524                if(object == null)
525                {
526                    json.writeValue(null);
527                    return;
528                }
529                json.writeValue(object.serializeToString());
530            }
531
532            @Override
533            public FakeLanguageGen read(Json json, JsonValue jsonData, Class type) {
534                if(jsonData == null || jsonData.isNull())
535                    return null;
536                return FakeLanguageGen.deserializeFromString(jsonData.asString());
537            }
538        });
539    }
540}