public class DwarvesBand { List<Dwarf> dwarves = new LinkedList<>(); // getters & setters } public class Dwarf { private String name; private FacialHair facialHair; private List<Weapon> weapons = new LinkedList<>(); private String lunch; private int dwarfAge; public Dwarf() { } public Dwarf(String name, int dwarfAge) { this.name = name; this.dwarfAge = dwarfAge; } // getters & setters } /** * */ public class FacialHair { private boolean haveBeard; private boolean haveMustache; private String color; public FacialHair(boolean haveBeard, boolean haveMustache, String color) { this.haveBeard = haveBeard; this.haveMustache = haveMustache; this.color = color; } // getters & setters } public class Weapon { private String type; public Weapon() { // do nothing } public Weapon(String type) { this.type = type; } // getters & setters } public class UniqueWeapon extends Weapon { private String name; private String origin; public UniqueWeapon() { super(); } public UniqueWeapon(String type, String name, String origin) { super(type); this.name = name; this.origin = origin; } // getters & setters }
public class BandUtil { public static DwarvesBand createBand() { DwarvesBand company = new DwarvesBand(); Dwarf tmpDwarf; tmpDwarf = new Dwarf("Orin", 90); tmpDwarf.setLunch("Ale with chicken"); tmpDwarf.setFacialHair(new FacialHair(true, true, "black")); tmpDwarf.addWeapon(new UniqueWeapon("sword", "Slasher", "Gondolin")); tmpDwarf.addWeapon(new UniqueWeapon("shield", "Oaken Shield", "Moria")); tmpDwarf.addWeapon(new Weapon("dagger")); company.addDwarf(tmpDwarf); tmpDwarf = new Dwarf("Kori", 60); // no lunch :( tmpDwarf.setFacialHair(new FacialHair(false, true, "red")); tmpDwarf.addWeapon(new Weapon("mace")); tmpDwarf.addWeapon(new Weapon("bow")); company.addDwarf(tmpDwarf); tmpDwarf = new Dwarf("Billy Bob", 45); tmpDwarf.setLunch("Ale with chicken and potatoes, tea with some cakes"); tmpDwarf.setFacialHair(new FacialHair(false, false, "")); company.addDwarf(tmpDwarf); return company; } }
toJson()
method. DwarvesBand band = BandUtil.createBand(); Gson gson = new GsonBuilder() .setPrettyPrinting() .create(); String json = gson.toJson(band);
Gson
class Gson
also be created through the new
operator, but then the output JSON would not be formatted, which is good for data exchange between applications (it forms faster, weighs less), but not healthy for human perception. Therefore, we used a special GsonBuilder by calling the setPrettyPrinting()
method, which allowed us to see the output JSON as follows: { "dwarves": [ { "name": "Orin", "facialHair": { "haveBeard": true, "haveMustache": true, "color": "black" }, "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, { "name": "Oaken Shield", "origin": "Moria", "type": "shield" }, { "type": "dagger" } ], "lunch": "Ale with chicken", "dwarfAge": 90 }, { "name": "Kori", "facialHair": { "haveBeard": false, "haveMustache": true, "color": "red" }, "weapons": [ { "type": "mace" }, { "type": "bow" } ], "dwarfAge": 60 }, { "name": "Billy Bob", "facialHair": { "haveBeard": false, "haveMustache": false, "color": "" }, "weapons": [], "lunch": "Ale with chicken and potatoes, tea with some cakes", "dwarfAge": 45 } ] }
{ "name": "Orin", "facialHair": "Black beard and mustache", "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, ... , "dagger" ], "age": 90 }
@SerializedName("age") private int dwarfAge;
lunch
box. First, you can do this by adding the transient keyword to it, in which case the field will not be taken into account during serialization. But not the fact that this is the right way. The fact that we do not need information about dinner here does not mean that it is not needed during some other serialization.serialize()
method: JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context)
T src
is the object to be serialized;Type typeOfSrc
- the type of the object being serialized;JsonSerializationContext context
- the context of serialization; The JsonSerializationContext
interface JsonSerializationContext
also functional and contains 1 method, also serialize()
; it should be used to process non-primitive data included in the object being serialized (and we will do this just below); context inherits all settings (including registered serializers, etc.) of the original Gson object.JsonElement
. This is an abstract class with 4 implementations, shown in the figure below:JsonNull
- the actual representation for null
JsonPrimitive
- representing primitive types like strings, numbers, etc.JsonArray
- a set of objects of type JsonElement
; can be viewed as a List<JsonElement>
; elements can be any of the JsonElement
implementations, and mixed types are supported;JsonObject
is a set of key-value pairs, where the key is a string and the value is again an object of type JsonElement
; similar to the Map<String, JsonElement>
.Dwarf
.FacialHair
.Weapon
and especially UniqueWeapon
, but we will leave it while on care of processing by default.JsonSerializer
. They look quite similar: public class DwarfSerializer implements JsonSerializer<Dwarf> { @Override public JsonElement serialize(Dwarf src, Type typeOfSrc, JsonSerializationContext context) { // ! return null; } } public class FacialHairSerializer implements JsonSerializer<FacialHair> { @Override public JsonElement serialize(FacialHair src, Type typeOfSrc, JsonSerializationContext context) { // ! return null; } }
registerTypeAdapter()
method of the GsonBuilder
class as follows: Gson gson = new GsonBuilder() .setPrettyPrinting() .registerTypeAdapter(Dwarf.class, new DwarfSerializer()) .registerTypeAdapter(FacialHair.class, new FacialHairSerializer()) .create();
public class FacialHairSerializer implements JsonSerializer<FacialHair> { @Override public JsonElement serialize(FacialHair src, Type typeOfSrc, JsonSerializationContext context) { if (!src.isHaveBeard() && !src.isHaveMustache()) return new JsonPrimitive("is he really a dwarf?"); List<String> list = new LinkedList<String>(); if (src.isHaveBeard()) { list.add("beard"); } if (src.isHaveMustache()) { list.add("mustache"); } return new JsonPrimitive( new StringBuilder(src.getColor()) .append(" ") .append(StringUtils.join(list, " and ")) .toString() ); } }
JsonPrimitive
object containing the desired string. if (!src.isHaveBeard() && !src.isHaveMustache()) return new JsonPrimitive("is he really a dwarf?");
JsonPrimitive
on its basis. And yes, let us take for granted that the input object and hair color are always initialized with us, so as not to complicate the code with checks that are completely unimportant for the purposes of the article. public class DwarfSerializer implements JsonSerializer<Dwarf> { @Override public JsonElement serialize(Dwarf src, Type typeOfSrc, JsonSerializationContext context) { JsonObject result = new JsonObject(); result.addProperty("name", src.getName()); result.addProperty("age", src.getDwarfAge()); result.add("facialHair", context.serialize(src.getFacialHair())); JsonArray weapons = new JsonArray(); result.add("weapons", weapons); for(Weapon weapon : src.getWeapons()) { weapons.add( weapon instanceof UniqueWeapon ? context.serialize(weapon) : new JsonPrimitive(weapon.getType()) ); } return result; } }
JsonObject result = new JsonObject();
JsonPrimitive
object). We pass two parameters to the method: the first is the key, that is, the name of the property of the JSON object, the second is the value of this property. This is where we set the name of the property “age” instead of “dwarfAge”, and also exclude information about lunch from the result — simply by not adding it to the resulting object. result.addProperty("name", src.getName()); result.addProperty("age", src.getDwarfAge());
serialize()
method — as mentioned earlier, the context is aware of the registered serializers, so our FacialHairSerializer
FacialHair
apply to the FacialHairSerializer
. We add the resulting JsonElement
to our object using the add () method, specifying the desired property name. result.add("facialHair", context.serialize(src.getFacialHair()));
JsonArray
to store them and add it to our object using the same add () method. JsonArray weapons = new JsonArray(); result.add("weapons", weapons);
JsonArray
class also has a add () method, but it accepts only one parameter of the JsonElement
type, which is logical - the key is not needed in this case. When adding a conventional weapon, we create a JsonPrimitive
based on a string, and uniquely serialize it using a context. In this case, the standard serialization mechanism will work, because we have not registered any handlers for the UniqueWeapon
class. weapons.add( weapon instanceof UniqueWeapon ? context.serialize(weapon) : new JsonPrimitive(weapon.getType()) );
DwarvesBand band = BandUtil.createBand(); Gson gson = new GsonBuilder() .setPrettyPrinting() .registerTypeAdapter(Dwarf.class, new DwarfSerializer()) .registerTypeAdapter(FacialHair.class, new FacialHairSerializer()) .create(); String json = gson.toJson(band);
{ "dwarves": [ { "name": "Orin", "age": 90, "facialHair": "black beard and mustache", "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, { "name": "Oaken Shield", "origin": "Moria", "type": "shield" }, "dagger" ] }, { "name": "Kori", "age": 60, "facialHair": "red mustache", "weapons": [ "mace", "bow" ] }, { "name": "Billy Bob", "age": 45, "facialHair": "is he really a dwarf?", "weapons": [] } ] }
{ "Kori": { "age": 60, "facialHair": "red mustache", "weapons": [ ... ] }, ... }
public class DwarvesBandSerializer implements JsonSerializer<DwarvesBand> { @Override public JsonElement serialize(DwarvesBand src, Type typeOfSrc, JsonSerializationContext context) { JsonObject result = new JsonObject(); for(Dwarf dwarf : src.getDwarves()) { result.add(dwarf.getName(), context.serialize(dwarf)); } return result; } }
DwarfSerializer
class) by deleting the line: result.addProperty("name", src.getName());
registerTypeAdapter()
method of the GsonBuilder
class: .registerTypeAdapter(DwarvesBand.class, new DwarvesBandSerializer())
{ "Orin": { "age": 90, "facialHair": "black beard and mustache", "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, { "name": "Oaken Shield", "origin": "Moria", "type": "shield" }, "dagger" ] }, "Kori": { "age": 60, "facialHair": "red mustache", "weapons": [ "mace", "bow" ] }, "Billy Bob": { "age": 45, "facialHair": "is he really a dwarf?", "weapons": [] } }
fromJson()
method. It takes two parameters: data in several formats (including String
, which we will use) and the type of the returned result. However, if we try to simply create a Gson object and call this method, as shown below, we will get an instance of the DwarvesBand
class with an empty list of gnomes: DwarvesBand dwarvesBand = new Gson().fromJson(json, DwarvesBand.class);
T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
JsonElement json
- Json element to restore data from;Type typeOfT
- the type of the object that should be the result;JsonDeserializationContext context
- deserialization context; similar to JsonSerializationContext
, the JsonDeserializationContext
interface contains one deserialize()
method; this context inherits all settings of the Gson object public class FacialHairDeserializer implements JsonDeserializer<FacialHair> { @Override public FacialHair deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { FacialHair hair = new FacialHair(); String data = json.getAsString(); List<String> parts = Arrays.asList(data.split(" ")); if(parts.contains("beard")) hair.setHaveBeard(true); if(parts.contains("mustache")) hair.setHaveMustache(true); if(hair.isHaveBeard() || hair.isHaveMustache()) hair.setColor(parts.get(0)); return hair; } }
String data = json.getAsString();
JsonElement
to a string if it is applied to an element of type JsonPrimitive
containing a valid string, or to a JsonArray
containing only one such element of type JsonPrimitive
. Otherwise, the method will throw an exception. All methods of the getAs{JavaType}()
type getAs{JavaType}()
.JsonPrimitive
with a string at the input, so we will not check it (one could use the isJsonPrimitive()
method). Further processing of the data obtained is trivial, we will not linger on it. public class DwafDeserializer implements JsonDeserializer<Dwarf> { @Override public Dwarf deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); Dwarf dwarf = new Dwarf(); dwarf.setDwarfAge(jsonObject.get("age").getAsInt()); dwarf.setFacialHair((FacialHair) context.deserialize(jsonObject.get("facialHair"), FacialHair.class)); JsonArray weapons = jsonObject.getAsJsonArray("weapons"); for(JsonElement weapon : weapons) { if(weapon.isJsonPrimitive()) { dwarf.addWeapon(new Weapon(weapon.getAsString())); } else { dwarf.addWeapon((UniqueWeapon) context.deserialize(weapon, UniqueWeapon.class)); } } return dwarf; } }
JsonObject
, so we will convert the input data to this type without checking. JsonObject jsonObject = json.getAsJsonObject();
JsonElement
age using the get()
method first, which returns us a JsonElement
with the value of the specified property “age”, and then the getAsInt()
method, since age has an integer type. dwarf.setDwarfAge(jsonObject.get("age").getAsInt());
FacialHair
using context.deserialize()
. As we remember, the context is aware of the need to use a special deserializer to process beard information. dwarf.setFacialHair((FacialHair) context.deserialize(jsonObject.get("facialHair"), FacialHair.class));
JsonElement
with the get method (“weapons”), then check for the array type using the isJsonArray()
method, and then convert it into an array using the getAsJsonArray()
method. But we believe in our gnomes and the format of their input. JsonArray weapons = jsonObject.getAsJsonArray("weapons");
for(JsonElement weapon : weapons) { if(weapon.isJsonPrimitive()) { dwarf.addWeapon(new Weapon(weapon.getAsString())); } else { dwarf.addWeapon((UniqueWeapon) context.deserialize(weapon, UniqueWeapon.class)); } }
JsonPrimitive
type. We remember that conventional weapons are described with a simple string, which corresponds to this type. In this case, create an instance of a conventional weapon, getting its type using the getAsString()
method. Otherwise, we are dealing with a unique weapon. We processed it using context using standard Gson mechanisms. We do the same thing now using context.deserialize()
. public class DwarvesBandDeserializer implements JsonDeserializer<DwarvesBand> { @Override public DwarvesBand deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { DwarvesBand result = new DwarvesBand(); JsonObject jsonObject = json.getAsJsonObject(); for(Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) { Dwarf dwarf = context.deserialize(entry.getValue(), Dwarf.class); dwarf.setName(entry.getKey()); result.addDwarf(dwarf); } return result; } }
JsonObject
input data to the JsonObject
type. Remember, it was previously mentioned that JsonObject
can be perceived as Map<String, JsonElement>
? Similar to Map
, JsonObject
has an entrySet()
method that returns a set of key-value elements. Just with his help, we will cycle through all the records about the gnomes. Dwarf dwarf = context.deserialize(entry.getValue(), Dwarf.class);
dwarf.setName(entry.getKey());
Gson gson = new GsonBuilder() .registerTypeAdapter(DwarvesBand.class, new DwarvesBandDeserializer()) .registerTypeAdapter(FacialHair.class, new FacialHairDeserializer()) .registerTypeAdapter(Dwarf.class, new DwafDeserializer()) .create();
DwarvesBand company = BandUtil.createBand(); Gson gson; gson = new GsonBuilder() .registerTypeAdapter(Dwarf.class, new DwarfSerializer()) .registerTypeAdapter(FacialHair.class, new FacialHairSerializer()) .registerTypeAdapter(DwarvesBand.class, new DwarvesBandSerializer()) .create(); String json = gson.toJson(company); gson = new GsonBuilder() .registerTypeAdapter(DwarvesBand.class, new DwarvesBandDeserializer()) .registerTypeAdapter(FacialHair.class, new FacialHairDeserializer()) .registerTypeAdapter(Dwarf.class, new DwafDeserializer()) .create(); DwarvesBand bandIsBack = gson.fromJson(json, DwarvesBand.class); gson = new GsonBuilder() .setPrettyPrinting() .create(); System.out.println(gson.toJson(bandIsBack));
{ "dwarves": [ { "name": "Orin", "facialHair": { "haveBeard": true, "haveMustache": true, "color": "black" }, "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, { "name": "Oaken Shield", "origin": "Moria", "type": "shield" }, { "type": "dagger" } ], "dwarfAge": 90 }, { "name": "Kori", "facialHair": { "haveBeard": false, "haveMustache": true, "color": "red" }, "weapons": [ { "type": "mace" }, { "type": "bow" } ], "dwarfAge": 60 }, { "name": "Billy Bob", "facialHair": { "haveBeard": false, "haveMustache": false, "color": "" }, "weapons": [], "dwarfAge": 45 } ] }
JsonElement
, which JsonElement
kindly provided us.write()
and read()
. They are responsible for custom transformations: write()
- for serialization, and read()
- for deserialization.TypeAdapter
for the entire list of weapons, not just for unique copies. Our class will look like this: public class WeaponsTypeAdapter extends TypeAdapter<List<Weapon>> { @Override public void write(JsonWriter out, List<Weapon> value) throws IOException { // Java → JSON } @Override public List<Weapon> read(JsonReader in) throws IOException { // JSON → Java return null; } }
.registerTypeAdapter()
. However, there is a snag. The first parameter of the method - is a data type for which the handler is registered, and gnome weapons we sold the usual list: List<Weapon>
. And we obviously do not want all other lists to be processed by our TypeAdapter. It is necessary to somehow indicate that it is intended only for the list of weapons, passing the parameterized type. For this, Gson uses a special tricky class - TypeToken <T> . With it, we can get the parameterized type we need as follows: Type weaponsListType = new TypeToken<List<Weapon>>(){}.getType();
TypeToken
anonymous class, in order to then getGenericSuperclass()
obtain the type of the parameterizing parent by method . In our case, the parameterizing parent type is ours List<Weapon>
. A little confusing, but in a different way, alas, no way. In more detail about obtaining the parameters of the Generic classes can be read, for example, in this article . Type weaponsListType = new TypeToken<List<Weapon>>(){}.getType(); Gson gson = new GsonBuilder() .setPrettyPrinting() .registerTypeAdapter(Dwarf.class, new DwarfSerializerWithTypeAdapter()) .registerTypeAdapter(FacialHair.class, new FacialHairSerializer()) .registerTypeAdapter(DwarvesBand.class, new DwarvesBandSerializer()) .registerTypeAdapter(weaponsListType, new WeaponsTypeAdapter()) .create();
public class DwarfSerializerWithTypeAdapter implements JsonSerializer<Dwarf> { public JsonElement serialize(...) { ... Type weaponsType = new TypeToken<List<Weapon>>(){}.getType(); result.add("weapons", context.serialize(src.getWeapons(), weaponsType)); ... } } public class DwafDeserializerWithTypeAdapter implements JsonDeserializer<Dwarf> { public Dwarf deserialize(...) { ... Type weaponsType = new TypeToken<List<Weapon>>(){}.getType(); List<Weapon> weapons = context.deserialize(jsonObject.getAsJsonArray("weapons"), weaponsType); dwarf.addWeapons(weapons); ... } }
public class WeaponsTypeAdapter extends TypeAdapter<List<Weapon>> { @Override public void write(JsonWriter out, List<Weapon> value) throws IOException { out.beginArray(); for (Weapon weapon : value) { if (weapon instanceof UniqueWeapon) { UniqueWeapon uWeapon = (UniqueWeapon) weapon; out.beginObject(); out.name("name") .value(uWeapon.getName() + " from " + uWeapon.getOrigin()); out.name("type") .value(uWeapon.getType()); out.endObject(); } else { out.value(weapon.getType()); } } out.endArray(); } @Override public List<Weapon> read(JsonReader in) throws IOException { List<Weapon> result = new LinkedList<>(); in.beginArray(); while (in.hasNext()) { switch (in.peek()) { case STRING: result.add(createCommonWeapon(in)); break; case BEGIN_OBJECT: result.add(createUniqueWeapon(in)); break; default: in.skipValue(); break; } } return result; } private Weapon createCommonWeapon(JsonReader in) throws IOException { return new Weapon(in.nextString()); } private Weapon createUniqueWeapon(JsonReader in) throws IOException { UniqueWeapon weapon = new UniqueWeapon(); in.beginObject(); while (in.hasNext()) { switch (in.nextName()) { case "name": String[] tmp = in.nextString().split(" from "); weapon.setName(tmp[0]); if (tmp.length > 1) weapon.setOrigin(tmp[1]); break; case "type": weapon.setType(in.nextString()); break; default: in.skipValue(); break; } } in.endObject(); return weapon; } }
write()
. Its code is: public void write(JsonWriter out, List<Weapon> value) throws IOException { out.beginArray(); for (Weapon weapon : value) { if (weapon instanceof UniqueWeapon) { UniqueWeapon uWeapon = (UniqueWeapon) weapon; out.beginObject(); out.name("name") .value(uWeapon.getName() + " from " + uWeapon.getOrigin()); out.name("type") .value(uWeapon.getType()); out.endObject(); } else { out.value(weapon.getType()); } } out.endArray(); }
JsonWriter
allows you to create output JSON in streaming mode. First of all, we need an array where we will store weapon data. out.beginArray(); ... out.endArray();
value()
: out.value(weapon.getType());
name()
and methods in turn value()
. out.name("name") .value(uWeapon.getName() + " from " + uWeapon.getOrigin()); out.name("type") .value(uWeapon.getType());
read()
takes one parameter: public List<Weapon> read(JsonReader in) throws IOException {...}
beginObject() / endObject()
and methods beginArray() / endArray()
.nextName()
, their values ​​with the method next{Type}()
(for example, nextString()
). The elements of the arrays are also sorted by the method next{Type}()
.GsonReader
there is also a method peek()
that returns the type of the next node without processing it.read()
we get is: @Override public List<Weapon> read(JsonReader in) throws IOException { List<Weapon> result = new LinkedList<>(); in.beginArray(); while (in.hasNext()) { switch (in.peek()) { case STRING: result.add(createCommonWeapon(in)); break; case BEGIN_OBJECT: result.add(createUniqueWeapon(in)); break; default: in.skipValue(); break; } } in.endArray(); return result; }
skipValue()
. private Weapon createCommonWeapon(JsonReader in) throws IOException { return new Weapon(in.nextString()); }
nextString()
and create an object on its basis. private Weapon createUniqueWeapon(JsonReader in) throws IOException { UniqueWeapon weapon = new UniqueWeapon(); in.beginObject(); while (in.hasNext()) { switch (in.nextName()) { case "name": String[] tmp = in.nextString().split(" from "); weapon.setName(tmp[0]); if (tmp.length > 1) weapon.setOrigin(tmp[1]); break; case "type": weapon.setType(in.nextString()); break; default: in.skipValue(); break; } } in.endObject(); return weapon; }
nextName()
. For properties with the names "name" and "type" we have processing algorithms - we create on their basis instances of conventional and unique weapons. The remaining properties (if there are any), again, skip. DwarvesBand company = BandUtil.createBand(); Gson gson; Type weaponsType = new TypeToken<List<Weapon>>(){}.getType(); gson = new GsonBuilder() .registerTypeAdapter(Dwarf.class, new DwarfSerializerWithTypeAdapter()) .registerTypeAdapter(FacialHair.class, new FacialHairSerializer()) .registerTypeAdapter(DwarvesBand.class, new DwarvesBandSerializer()) .registerTypeAdapter(weaponsType, new WeaponsTypeAdapter()) .setPrettyPrinting() .create(); String json = gson.toJson(company); System.out.println("Serialized:"); System.out.println(json); gson = new GsonBuilder() .registerTypeAdapter(DwarvesBand.class, new DwarvesBandDeserializer()) .registerTypeAdapter(FacialHair.class, new FacialHairDeserializer()) .registerTypeAdapter(Dwarf.class, new DwafDeserializerWithTypeAdapter()) .registerTypeAdapter(weaponsType, new WeaponsTypeAdapter()) .create(); DwarvesBand companyIsBack = gson.fromJson(json, DwarvesBand.class); gson = new GsonBuilder() .setPrettyPrinting() .create(); System.out.println("\n\nDeserialized:"); System.out.println(gson.toJson(companyIsBack));
Serialized: { "Orin": { "age": 90, "facialHair": "black beard and mustache", "weapons": [ { "name": "Slasher from Gondolin", "type": "sword" }, { "name": "Oaken Shield from Moria", "type": "shield" }, "dagger" ] }, "Kori": { "age": 60, "facialHair": "red mustache", "weapons": [ "mace", "bow" ] }, "Billy Bob": { "age": 45, "facialHair": "is he really a dwarf?", "weapons": [] } } Deserialized: { "dwarves": [ { "name": "Orin", "facialHair": { "haveBeard": true, "haveMustache": true, "color": "black" }, "weapons": [ { "name": "Slasher", "origin": "Gondolin", "type": "sword" }, { "name": "Oaken Shield", "origin": "Moria", "type": "shield" }, { "type": "dagger" } ], "dwarfAge": 90 }, { "name": "Kori", "facialHair": { "haveBeard": false, "haveMustache": true, "color": "red" }, "weapons": [ { "type": "mace" }, { "type": "bow" } ], "dwarfAge": 60 }, { "name": "Billy Bob", "facialHair": { "haveBeard": false, "haveMustache": false, "color": "" }, "weapons": [], "dwarfAge": 45 } ] }
Source: https://habr.com/ru/post/228279/
All Articles