Create a dynamic nested JSON Java - java

I would like to create a Json out of an array of strings which each look like:
"id.categoryvalue.subcatvalue.subsubcatvalue.score"
Result should look like this:
{
"user-id": "u42",
"categories": [
{
"category": "value",
"subcats": [
{
"subcat": "value",
"subsubcats": [
{
"subsubcat": "value",
"score": 47
}
]
}
]
}
]
}
I don't want to duplicate a value in the same category, what would be an effective way to solve it? Thanks!

With Gson you can generate the JSON using either the object model way or the streaming way. The first one requires the whole object to be built before generating the JSON either in a strongly typed manner (see below) or a weakly typed one (like what Darshan Mehta suggested -- he used a weak objects based on Java maps).
Object model approach
Decompose the given data into several data bag classes, construct the objects of these classes, an just invoke Gson:
public final class ObjectModelDemo {
private ObjectModelDemo() {
}
public static void main(final String... args) {
final String[] tokens = "id31.english.B1.Animals.11".split("\\.");
if ( tokens.length != 5 ) {
throw new IllegalArgumentException("Not 5 tokens in " + "id31.english.B1.Animals.11");
}
final String userId = tokens[0];
final String category = tokens[1];
final String subcategory = tokens[2];
final String subsubcategory = tokens[3];
final int score = parseInt(tokens[4]);
final User user = new User(
userId,
singletonList(new Category(
category,
singletonList(new Subcategory(
subcategory,
singletonList(new Subsubcategory(subsubcategory, score))
))
))
);
final Gson gson = new GsonBuilder().create();
out.println(gson.toJson(user));
}
private static final class User {
#SerializedName("user-id")
private final String userId;
#SerializedName("categories")
private final List<Category> categories;
private User(final String userId, final List<Category> categories) {
this.userId = userId;
this.categories = categories;
}
}
private static final class Category {
#SerializedName("category")
private final String category;
#SerializedName("subcats")
private final List<Subcategory> subcategories;
private Category(final String category, final List<Subcategory> subcategories) {
this.category = category;
this.subcategories = subcategories;
}
}
private static final class Subcategory {
#SerializedName("subcat")
private final String subcategory;
#SerializedName("subsubcats")
private final List<Subsubcategory> subsubcategories;
private Subcategory(final String subcategory, final List<Subsubcategory> subsubcategories) {
this.subcategory = subcategory;
this.subsubcategories = subsubcategories;
}
}
private static final class Subsubcategory {
#SerializedName("subsubcat")
private final String subsubcategory;
#SerializedName("score")
private final int score;
private Subsubcategory(final String subsubcategory, final int score) {
this.subsubcategory = subsubcategory;
this.score = score;
}
}
}
Such data bags are called DTOs (data-transfer objects) and are designed to map between Java objects and their respective JSON representation. This gives you very strong compile-time control support by the Java compiler. Disadvantage of this approach is that you must collect all the data before any JSON generation begins (usually this is very crucial for memory-sensitive applications). However, for your case, this can be much and much easier, and probably the most easy one.
The output:
{"user-id":"id31","categories":[{"category":"english","subcats":[{"subcat":"B1","subsubcats":[{"subsubcat":"Animals","score":11}]}]}]}
Streaming approach
An alternative approach based on intermediate writing a JSON to a destination. This approach is a data-pushing approach that is more complex, however it is more efficient from the memory consumption point of view (even more, you can easily generate infinite JSONs using this method, but I doubt you'll ever have such a situation :) ).
public static void main(final String... args)
throws IOException {
final String[] tokens = "id21.english.B2.Insects.24".split("\\.");
if ( tokens.length != 5 ) {
throw new IllegalArgumentException("Not 5 tokens in " + "id21.english.B2.Insects.24");
}
final String userId = tokens[0];
final String category = tokens[1];
final String subcategory = tokens[2];
final String subsubcategory = tokens[3];
final int score = parseInt(tokens[4]);
final JsonWriter jsonWriter = new JsonWriter(new OutputStreamWriter(out));
writeUser(jsonWriter, userId, category, subcategory, subsubcategory, score);
jsonWriter.flush();
// do not invoke jsonWriter.close() -- let the caller decide what to do
}
private static void writeUser(final JsonWriter jsonWriter, final String userId, final String category, final String subcategory,
final String subsubcategory, final int score)
throws IOException {
jsonWriter.beginObject();
jsonWriter.name("user-id");
jsonWriter.value(userId);
jsonWriter.name("categories");
writeCategories(jsonWriter, category, subcategory, subsubcategory, score);
jsonWriter.endObject();
}
private static void writeCategories(final JsonWriter jsonWriter, final String category, final String subcategory, final String subsubcategory,
final int score)
throws IOException {
jsonWriter.beginArray();
jsonWriter.beginObject();
jsonWriter.name("category");
jsonWriter.value(category);
jsonWriter.name("subcats");
writeSubcategories(jsonWriter, subcategory, subsubcategory, score);
jsonWriter.endObject();
jsonWriter.endArray();
}
private static void writeSubcategories(final JsonWriter jsonWriter, final String subcategory, final String subsubcategory, final int score)
throws IOException {
jsonWriter.beginArray();
jsonWriter.beginObject();
jsonWriter.name("subcat");
jsonWriter.value(subcategory);
jsonWriter.name("subsubcats");
writeSubsubcategories(jsonWriter, subsubcategory, score);
jsonWriter.endObject();
jsonWriter.endArray();
}
private static void writeSubsubcategories(final JsonWriter jsonWriter, final String subsubcategory, final int score)
throws IOException {
jsonWriter.beginArray();
jsonWriter.beginObject();
jsonWriter.name("subsubcat");
jsonWriter.value(subsubcategory);
jsonWriter.name("score");
jsonWriter.value(score);
jsonWriter.endObject();
jsonWriter.endArray();
}
}
Pay attention on how data is written to the output stream: once you have enough known data, you can easily flush it to the output stream. Yep, this is a more hard-to-write approach, but this is an alternative.
The output:
{"user-id":"id21","categories":[{"category":"english","subcats":[{"subcat":"B2","subsubcats":[{"subsubcat":"Insects","score":24}]}]}]}

Assuming the format of input remains constant, you can do the following:
Tokenize the input string by splitting it with .
Declare/initialise the structures (e.g. Map and set appropriate values)
Convert resultant object to String using any JSON parser
Below example demonstrates it:
public static void main(String[] args) throws Exception{
Map<String, Object> result = convertToMap("id.categoryvalue.subcatvalue.subsubcatvalue.score");
System.out.println(new ObjectMapper().writeValueAsString(result));
}
public static Map<String, Object> convertToMap(String input){
Map<String, Object> result = new LinkedHashMap<>();
String[] tokens = input.split("\\.");
if(tokens.length < 5){
//Invalid string, return
throw new IllegalArgumentException("Insufficient tokens");
}
//Add user id
result.put("user-id", tokens[0]);
//Add category
Map<String, Object> category = new LinkedHashMap<>();
category.put("category", tokens[1]);
//Add sub category
Map<String, Object> subCategory = new LinkedHashMap<>();
subCategory.put("subcat", tokens[2]);
//Add nested category
Map<String, Object> nestedSubCategory = new LinkedHashMap<>();
nestedSubCategory.put("subsubcat", tokens[3]);
nestedSubCategory.put("score", Integer.parseInt(tokens[4]));
subCategory.put("subcats", Arrays.asList(new Map[] {nestedSubCategory}));
category.put("subcats", Arrays.asList(new Map[] {subCategory}));
result.put("categories", Arrays.asList(new Map[] {category}));
return result;
}

Related

Random string generator gets the same result in Java

Hello we are developing a Quiz game in Java.In our project we use Hashmap that consist of countries and their details.Each time in the loop we want to get three random countries with a random method, and we want User to answer questions related to that random country.Everytime user answers the questions related to the country we get another random country from hashmap.But we don't want these three random countries to be the same.How can we avoid that?
import java.util.HashMap;
import java.util.Random;
public class Countries {
private HashMap<String, String[]> countrieseasy;
private HashMap<String, String[]> countriesmedium;
private HashMap<String, String[]> countrieshard;
private String previousEasyCountry;
private String secondPreviousEasyCountry;
private String previousMediumCountry;
private String secondPreviousMediumCountry;
private String previousHardCountry;
private String secondPreviousHardCountry;
private Random random;
public Countries() {
countrieseasy = new HashMap<>();
countriesmedium = new HashMap<>(); // create a new HashMap object
countrieshard = new HashMap<>();
previousEasyCountry = null;
secondPreviousEasyCountry = null;
previousMediumCountry = null;
secondPreviousMediumCountry = null;
previousHardCountry = null;
secondPreviousHardCountry = null;
random = new Random();
}
public void addCountryeasy(String name, String capital, String language, String continent, String currency) {
String[] details = {name, capital, language, continent, currency};
countrieseasy.put(name, details);
}
public void addCountrymedium(String name, String capital, String language, String continent, String currency) {
String[] details = {name, capital, language, continent, currency};
countriesmedium.put(name, details);
}
public void addCountryhard(String name, String capital, String language, String continent, String currency) {
String[] details = {name, capital, language, continent, currency};
countrieshard.put(name, details);
}
public String[] getCountryeasyDetails(String name) {
return countrieseasy.get(name);
}
public String[] getCountrymediumDetails(String name) {
return countriesmedium.get(name);
}
public String[] getCountryhardDetails(String name) {
return countrieshard.get(name);
}
private String getRandomCountryName(HashMap<String, String[]> countries, String previousCountry, String secondPreviousCountry) {
Object[] countryNames = countries.keySet().toArray();
int randomIndex = random.nextInt(countryNames.length);
String countryName = (String) countryNames[randomIndex];
while (countryName.equals(previousCountry) || ( countryName.equals(secondPreviousCountry)) || (secondPreviousCountry != null && secondPreviousCountry.equals(previousCountry))) {
randomIndex = random.nextInt(countryNames.length);
countryName = (String) countryNames[randomIndex];
}
return countryName;
}
public String getRandomCountryeasyName() {
String secondPreviousCountry = previousEasyCountry;
previousEasyCountry = secondPreviousEasyCountry;
secondPreviousEasyCountry = secondPreviousCountry;
String countryName = getRandomCountryName(countrieseasy, previousEasyCountry, secondPreviousEasyCountry);
return countryName;
}
public String getRandomCountrymediumName() {
String secondPreviousCountry = previousMediumCountry;
previousMediumCountry = secondPreviousMediumCountry;
secondPreviousMediumCountry = secondPreviousCountry;
String countryName = getRandomCountryName(countriesmedium, previousEasyCountry, secondPreviousEasyCountry);
return countryName;
}
public String getRandomCountryhardName() {
String secondPreviousCountry = previousMediumCountry;
previousHardCountry = secondPreviousHardCountry;
secondPreviousHardCountry = secondPreviousCountry;
String countryName = getRandomCountryName(countriesmedium, previousEasyCountry, secondPreviousEasyCountry);
return countryName;
}
}
So here is our code we tried to give each country a string value previousName , and ckecked if it is equal to the next random country we get from hashmap but it didn't work.

gson deserializing a field as null

First of all, I'd like to say this is for a university project.
I have 3 classes. Order abstract class and Delivery and DineIn classes which inherits from Order.
I am using Gson to serialize/deserialize the child classes but I have run into a bit of a problem. The Order class has a field orderType which gson uses to determine type of order it is, DineIn or Delivery.
Serialization is working just fine. The problem is that whenever I try to deserialize, the type field value is not read and is always set as null even though it is present in the JSON file.
This happens when there are a lot of fields in Order because when I tried testing this program on a smaller scale with the Order class just having 2 fields (orderType and orderNo) everything worked just fine. I don't what I am doing wrong. I have tried searching on this site and am almost always coming across suggestions to make custom type adapters and serializers but we haven't studied about them in university and I don't want to use them (the instructor deducts marks for using anything he hasn't taught, I almost failed a course I took from him last time because I used things he hadn't taught. He doesn't seem to have a problem with third-party libraries though).
The code:
public class Main {
public static final List<Order> ordersList = read();
public static void main(String[] args) {
System.out.println(ordersList.get(0).getOrderType());
System.out.println(ordersList.get(0) instanceof DineIn ? "DineIn": "Delivery");
}
private static List<Order> read(){
List<Order> ordersList = new ArrayList<>();
Type type = new TypeToken<ArrayList<Order>>() {
}.getType();
RuntimeTypeAdapterFactory<Order> adapter = RuntimeTypeAdapterFactory.of(Order.class, "orderType")
.registerSubtype(DineIn.class)
.registerSubtype(Delivery.class);
Gson gson = new GsonBuilder().registerTypeAdapterFactory(adapter).create();
JsonReader ordersJsonReader;
try {
ordersJsonReader = new JsonReader(new FileReader("orders.json"));
List<Order> tempOrdersList = gson.fromJson(ordersJsonReader, type);
if (tempOrdersList != null) ordersList = tempOrdersList;
ordersJsonReader.close();
} catch (IOException e) {
e.printStackTrace();
}
return ordersList;
}
}
abstract class Order {
private final int orderNumber;
private final String date, customerName;
private final int discountRate;
private final String paymentMethod;
private String orderStatus;
private int grossTotal = 0;
private double netTotal = 0;
private int totalItems = 0;
protected final String orderType;
public abstract String getOrderType();
public abstract double getExtraCharges();
public Order(int orderNumber, String date, String customerName, int discountRate, String paymentMethod, String orderStatus, int grossTotal, double netTotal, int totalItems, String orderType) {
this.orderNumber = orderNumber;
this.date = date;
this.customerName = customerName;
this.discountRate = discountRate;
this.paymentMethod = paymentMethod;
this.orderStatus = orderStatus;
this.grossTotal = grossTotal;
this.netTotal = netTotal;
this.totalItems = totalItems;
this.orderType = orderType;
}
}
class DineIn extends Order {
private double serviceCharges = 150;
public DineIn(int orderNumber, String date, String customerName, int discountRate, String paymentMethod, String orderStatus, int grossTotal, double netTotal, int totalItems) {
super(orderNumber, date, customerName, discountRate, paymentMethod, orderStatus, grossTotal, netTotal, totalItems, "DineIn");
}
#Override
public String getOrderType() {
return orderType;
}
#Override
public double getExtraCharges() {
return serviceCharges;
}
}
class Delivery extends Order {
private double deliveryCharges = 100;
public Delivery(int orderNumber, String date, String customerName, int discountRate, String paymentMethod, String orderStatus, int grossTotal, double netTotal, int totalItems) {
super(orderNumber, date, customerName, discountRate, paymentMethod, orderStatus, grossTotal, netTotal, totalItems, "Delivery");
}
#Override
public String getOrderType() {
return orderType;
}
#Override
public double getExtraCharges() {
return deliveryCharges;
}
}
The JSON:
[
{
"serviceCharges": 150.0,
"orderNumber": 1,
"date": "12/12/2021",
"customerName": "Ali",
"discountRate": 15,
"paymentMethod": "Card",
"orderStatus": "Preparing",
"grossTotal": 5000,
"netTotal": 4500.0,
"totalItems": 14,
"orderType": "DineIn"
}
]
In your code you have a hierarchy where DineIn and Delivery extend from Order. The way the orderType field is set is through an explicit String argument in the super() constructor.
However, Gson does not use the constructor to instantiate the objects. It uses a special no-argument constructor and populates the values via reflection: https://stackoverflow.com/a/40442037/9698467
In this specific case the problem comes from the RuntimeTypeAdapterFactory, which removes the orderType field from the JSON that it reads. The source code here confirms that: https://github.com/google/gson/blob/86d88c32cf6a6b7a6e0bbc855d76e4ccf6f120bb/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java#L202
As #fluffy suggested newer versions of the library include the maintain flag, which should allow for the field to be preserved: https://github.com/google/gson/blob/c1e7e2d2808b042cbe47ca31869ee6ccc62c5417/extras/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java#L214

How to call a string array into a method

I have defined 3 methods in which there are String[] as parameters. I would like to set these ones from another function, with some strings, and printing each one.
When I try to call these methods from another function(the start() function), to set the values of String[] parameters Eclipse says:
Type mismatch: cannot convert from String
to String[]
public class logistics2 {
private static String plane;
private static String truck;
private static String airport[];
private static String loc[];
private static String city[];
private static String pack[];
public static void at_obj_place(String object, String[] place) {
object = truck;
object = plane;
place = loc;
place = city;
place = airport;
return;
}
public static void in_pk_vehicle(String[] Pack, String vehicle) {
Pack = pack;
vehicle = truck;
vehicle = plane;
return;
}
public static void in_city(String[] place, String[] City ) {
place = loc;
City = city;
return;
}
public static void start() {
// HERE I RECEIVE THE ERROR MESSAGE
in_city(airport = "cdg", city = "paris");
in_city(airport = "lhr", city = "london");
in_city(loc = "north", city = "paris");
in_city(loc = "south",city = "paris");
at_obj_place(plane = "plane", airport = "lhr");
at_obj_place(truck = "truck", airport = "cdg");
at_obj_place(pack1 = "p1", airport = "lhr");
at_obj_place(pack2 = "p2", airport = "lhr");
for(int i = 0; i < airport[].length(); ) {
System.out.println(airport + " " + city);
}
return;
I would like to print each of the values that I have set with the for condition, based on the count of the values number that I have inserted in the String[]
In all the methods you have to passed strings you have to pass array of strings. Like this for all arrays:
airport[0] = "cdg";
airport[1] = "lhr";
city[0] = "paris";
city[1] = "london";
then pass this to methods like:
in_city(airport,city);
And in for loop you have to mention only array name like airport not airport[] and add increment for i.

Advanced string manipulation from job online

I just did a company online and i couldnt get this question right..
looking to improve for next online assessment.
question is:
your given 1 input string where the format is like this:
current:target
where current = Company,Stock or Bond,amount
and target = Company,Stock or Bond,amount
and the ':' separates current and target
an example is:
Vodafone,STOCK,10|Google,STOCK,15|Microsoft,BOND,15:Vodafone,STOCK,15|Google,STOCK,10|Microsoft,BOND,15
the output should be a string that makes the input meet the output for this case its going to be:
SELL,Google,STOCK,5
BUY,Vodafone,STOCK,5
as you can see the ouput should be in alphabetical order (google is before vodafone) and also bonds should appear before stocks.
here is some setup code to help you:
public class main {
public static void main(String[] args){
String input = "Vodafone,STOCK,10|Google,STOCK,15|Microsoft,BOND,15:Vodafone,STOCK,15|Google,STOCK,10|Microsoft,BOND,15";
String output = matchBenchmark(input);
System.out.println(output);
}
public static String matchBenchmark(String input){
}
}
You should probably parse the input into a map:
Map keys should be <company>,<type>
Map values can be {<current-value>,<new-value>}
Then iterate over the map (you probably want to sort the keys) and print SELL or BUY according to the different between the current and new value.
Smth. like that:
public final class Market {
private final Pattern patternLine = Pattern.compile("(?<current>.+):(?<target>.+)");
private final Pattern patternData = Pattern.compile("(?<name>\\w+),(?<active>\\w+),(?<amount>[-]?\\d+)");
private final Pattern patternDataSplit = Pattern.compile("\\|");
public String matchBenchmark(String input) {
Matcher matcher = patternLine.matcher(input);
if (!matcher.matches())
throw new IllegalArgumentException();
Map<String, Data> current = getData(matcher.group("current"));
Map<String, Data> target = getData(matcher.group("target"));
return calc(current, target).stream()
.map(data -> data.getName() + ',' + data.getActive() + ',' + data.getAmount())
.collect(Collectors.joining("\n"));
}
private Map<String, Data> getData(String str) {
return Arrays.stream(patternDataSplit.split(str))
.map(patternData::matcher)
.filter(Matcher::matches)
.map(m -> new Data(m.group("name"), m.group("active"), Integer.parseInt(m.group("amount"))))
.collect(Collectors.toMap(Data::getName, Function.identity()));
}
private static final String BUY = "BUY";
private static final String SELL = "SELL";
private static Queue<Data> calc(Map<String, Data> current, Map<String, Data> target) {
Queue<Data> queue = new PriorityQueue<>(Data.SORT_BY_NAME_ACTIVE_ASC);
Set<String> names = new HashSet<>(current.keySet());
names.addAll(target.keySet());
for (String name : names) {
Data dataCurrent = current.get(name);
Data dataTarget = target.get(name);
if (dataCurrent == null) {
if (dataTarget.amount != 0)
queue.add(new Data(dataTarget.amount > 0 ? BUY : SELL, dataTarget.name, Math.abs(dataTarget.amount)));
} else if (dataTarget == null) {
if (dataCurrent.amount != 0)
queue.add(new Data(dataCurrent.amount > 0 ? SELL : BUY, dataCurrent.name, Math.abs(dataCurrent.amount)));
} else if (dataCurrent.amount != dataTarget.amount)
queue.add(new Data(dataTarget.amount > dataCurrent.amount ? BUY : SELL, dataCurrent.name,
Math.abs(dataTarget.amount - dataCurrent.amount)));
}
return queue;
}
public static final class Data {
public static final Comparator<Data> SORT_BY_NAME_ACTIVE_ASC = Comparator.comparing(Data::getActive).thenComparing(Data::getName);
private final String name;
private final String active;
private final int amount;
public Data(String name, String active, int amount) {
this.name = name;
this.active = active;
this.amount = amount;
}
public String getName() {
return name;
}
public String getActive() {
return active;
}
public int getAmount() {
return amount;
}
}
}

How to serialize a special json with Gson (content of object instand of object)

I use Java + Gson and I want serialize a special json. I want content of object instand of object.
{
"age": 26,
"email": "norman#futurestud.io",
"isDeveloper": true,
"name": "Norman",
"userAddress": {
"city": "Magdeburg",
"country": "Germany",
"houseNumber": "42A",
"street": "Main Street"
}
}
is it possible serialize to:
{
"age": 26,
"email": "norman#futurestud.io",
"isDeveloper": true,
"name": "Norman",
"city": "Magdeburg",
"country": "Germany",
"houseNumber": "42A",
"street": "Main Street"
}
I try use this code but wrong:
#Expose
private final String age;
...
#Expose
#JsonAdapter(UserAddressSerializer.class)
private final UserAddress userAddress;
...
public class UserAddressSerializer implements JsonSerializer<UserAddress > {
#Override
public JsonElement serialize(UserAddress src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
result.add("name", new JsonPrimitive(src.getName()));
result.add("city", new JsonPrimitive(src.getCity()));
result.add("country", new JsonPrimitive(src.getCountry()));
result.add("houseNumber", new JsonPrimitive(src.getHouseNumber()));
result.add("street", new JsonPrimitive(src.getStreet()));
return result;
}
}
JSON trees are what you are looking for.
But at least one caveat here is sharing same-named property names among different objects.
Suppose you have the following data classes:
final class User {
final String email;
final String name;
final int age;
final boolean isDeveloper;
User(final String email, final int age, final String name, final boolean isDeveloper) {
this.age = age;
this.email = email;
this.isDeveloper = isDeveloper;
this.name = name;
}
#Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("email", email)
.add("name", name)
.add("age", age)
.add("isDeveloper", isDeveloper)
.toString();
}
}
final class UserAddress {
final String country;
final String city;
final String street;
final String houseNumber;
UserAddress(final String country, final String city, final String street, final String houseNumber) {
this.country = country;
this.city = city;
this.street = street;
this.houseNumber = houseNumber;
}
#Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("country", country)
.add("city", city)
.add("street", street)
.add("houseNumber", houseNumber)
.toString();
}
}
Now you can have an utility method to serialize multiple objects into a single JsonObject instance.
Also, you can easily have a deserialization counter-part that can deserialize multiple objects from a JsonObject instance.
For example:
final class JsonObjects {
private JsonObjects() {
}
#SafeVarargs
static JsonObject combine(final Gson gson, final Map.Entry<Object, ? extends Type>... objectAndTypeEntries) {
return Stream.of(objectAndTypeEntries)
// Each element should be serialized to a JsonElement
.map(e -> gson.toJsonTree(e.getKey(), e.getValue()))
// That must be a JSON object
.map(JsonElement::getAsJsonObject)
// And now we can collect a stream of JsonObject instances into a super-object
.collect(Collector.of(
JsonObject::new, // We'll collect into a new JsonObject instance, this is the accumulator
JsonObjects::mergeInto, // Merge each consecutive JsonObject instance into the accumulator
JsonObjects::mergeInto, // Or even combine multiple accumulators
Function.identity() // And just return the accumulator
));
}
static List<?> split(final Gson gson, final JsonObject jsonObject, final Type... types) {
return Stream.of(types)
// Each element should be deserialized from the JsonObject by a type
.map(type -> gson.fromJson(jsonObject, type))
// And just collect all the deserialized objects to a list
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
}
private static JsonObject mergeInto(final JsonObject to, final JsonObject from) {
for ( final Map.Entry<String, JsonElement> e : from.entrySet() ) {
to.add(e.getKey(), e.getValue());
}
return to;
}
}
Example of use:
final User beforeUser = new User("norman#localhost", 26, "Norman", true);
System.out.println("BEFORE User: " + beforeUser);
final UserAddress beforeUserAddress = new UserAddress("Germany", "Magdeburg", "Main Street", "42A");
System.out.println("BEFORE User address: " + beforeUserAddress);
final JsonObject jsonObject = JsonObjects.combine(
gson,
new AbstractMap.SimpleImmutableEntry<>(beforeUser, User.class),
new AbstractMap.SimpleImmutableEntry<>(beforeUserAddress, UserAddress.class)
);
System.out.println("JSON: " + jsonObject);
final List<?> objects = JsonObjects.split(gson, jsonObject, User.class, UserAddress.class);
final User afterUser = (User) objects.get(0);
System.out.println("AFTER User: " + afterUser);
final UserAddress afterUserAddress = (UserAddress) objects.get(1);
System.out.println("AFTER User address: " + afterUserAddress);
Output:
BEFORE User: User{email=norman#localhost, name=Norman, age=26, isDeveloper=true}
BEFORE User address: UserAddress{country=Germany, city=Magdeburg, street=Main Street, houseNumber=42A}
JSON: {"email":"norman#localhost","name":"Norman","age":26,"isDeveloper":true,"country":"Germany","city":"Magdeburg","street":"Main Street","houseNumber":"42A"}
AFTER User: User{email=norman#localhost, name=Norman, age=26, isDeveloper=true}
AFTER User address: UserAddress{country=Germany, city=Magdeburg, street=Main Street, houseNumber=42A}
As you can see, you don't need to create extra DTO classes to cover all the cases, but this also requires a little more memory usage due to the use of trees here.
I find a solution with use Gson tools (class UserAddressAdapter extends TypeAdapter<UserAddress>).
public class User {
...
// see the first ligne of UserAddressAdapter#write
// city => (out.value(value.getCity());)
#SerializedName("city")
#JsonAdapter(UserAddressAdapter.class)
private final UserAddress userAddress;
...
}
public class UserAddressAdapter extends TypeAdapter<UserAddress> {
#Override
public void write(JsonWriter out, UserAddress value) throws IOException {
out.value(value.getCity());
out.name("country");
out.value(value.getCountry());
out.name("houseNumber");
out.value(value.getHouseNumber());
out.name("street");
out.value(value.getStreet());
}
#Override
public UserAddress read(JsonReader in) throws IOException {
final int city = in.nextInt();
UserAddress[] scopes = UserAddress.values();
for (UserAddress scope : scopes) {
if (scope.getCity() == city) {
return scope;
}
}
return null;
}
}
and caller is:
new Gson().toJson(new User("...", ...))

Categories

Resources