I need to make a Builder class in which I need to have below fields so when I populate those fields in my Builder class and then if I call toJson method on it which I need to create as well, then it should make json structure like as shown below:
{
"id": "hello",
"type": "process",
"makers": {
"typesAndCount": {
"abc": 4,
"def": 3,
"pqr": 2
}
}
}
Key in my above JSON is fixed always only the values will change. But in typesAndCount field I have three different keys abc, def and pqr. Sometimes I will have one key there or two keys or all the keys. So stuff in typesAndCount key can change depending on what's being passed. Below is also possible case.
{
"id": "hello",
"type": "process",
"makers": {
"typesAndCount": {
"abc": 4,
"def": 3,
}
}
}
I started with below code in my Builder class but not sure how should I proceed further.
public class Key {
private final String id;
private final String type;
// confuse now
}
I just want to populate data in my class and then call some method it can be toJson to make string in above JSON format.
User Builder pattern for fluent configure your data builder. E.g.
class Builder {
private final String id;
private final String type;
private Map<String, Integer> map = new HashMap<>();
// mandatory fields are always passed through constructor
Builder(String id, String type) {
this.id = id;
this.type = type;
}
Builder typeAndCount(String type, int count) {
map.put(type, count);
return this;
}
JsonObject toJson() {
JsonObjectBuilder internal = null;
if (!map.isEmpty()) {
internal = Json.createObjectBuilder();
for (Map.Entry<String, Integer> e: map.entrySet()) {
internal.add(e.getKey(), e.getValue());
}
}
// mandatory fields
JsonObjectBuilder ob = Json.createObjectBuilder()
.add("id", id)
.add("type", type);
if (internal != null) {
ob.add("makers", Json.createObjectBuilder().add("typesAndCount", internal));
}
return ob.build();
}
public static void main(String[] args) {
Builder b = new Builder("id_value", "type_value")
.typeAndCount("abs", 1)
.typeAndCount("rty", 2);
String result = b.toJson().toString();
System.out.println(result);
}
}
As you can see you can call typeAndCount as many times as you need or even don't call it at all. toJson method handles this without any problem.
UPDATE: The output for example in method main is
{"id":"id_value","type":"type_value","makers":{"typesAndCount":{"abs":1,"rty":2}}}
UPDATE 2: the builder without 'typeAndCount` method call at all will produce this output
{"id":"id_value","type":"type_value"}
Related
I'm using a very strange api, it's data field type is dynamic.
If error occured, the data field will be a string like this:
{
"code": 2001,
"data": "Error!"
}
And if success, the data field will be a object:
{
"code": 2000,
"data": {
"id": 1,
"name": "example"
}
}
I'm using the following Kotlin code to de-serialize it:
return Gson().fromJson(data, SimpleModel::class.java)
The model definition is down below:
import com.google.gson.annotations.SerializedName
class SimpleModel {
#SerializedName("code")
val code = 0
#SerializedName("data")
val data: SimpleData = SimpleData()
}
class SimpleData {
#SerializedName("id")
val id = ""
#SerializedName("name")
val name = ""
}
When no error occred, the code above works just fine. However when error occured, exception was thrown:
java.lang.IllegalStateException: Excepted BEGIN_OBJECT but was STRING at line 1 column x $path.data
Is there a way to de-serialize data field to an object or just anything and determine it's type by code manually?
You would need to write a custom deserializer, which decides what type to deserialize data into, depending on the runtime type of the node, and register that deserializer with your Gson instance. Unfortunately i am not familiar with kotlin syntax, so i can only give you pseudo code.
Field data in SimpleModel should be either Object, or make the class generic - SimpleModel<T>, and the field should be of type T as well.
Parse the input to gson's node type - JsonElement.
Get data field
JsonElement root = parseResponse();
root.getAsJsonObject().get("data").getAsString();
Use getAs...() methods to check type.
Get as string. If success, it's a string and set the string value in SimpleModel.
If you get exception getting as string, get it as object - getAsJsonObject(), parse the object to SimpleData and set this new object in SimpleModel.
You could use my my answer here as inspiration. Although it's about object mapper, it does the same thing - decides object type depending on the node type, and follows roughly the same algorithm i described above.
Also this guide has info about how to write yor own deserialzer and registering it.
Like the previous answer, I am not familiarized with Kotlin, and the following solution is in Java, but as I know it is easy to convert Java to Kotlin using IntelliJ built-in tools.
The success/error objects pair is a classic problem, and you can create your own way to solve it, but let's consider the following classes represent the success and error objects respectively (Java 17, pattern matching on switch enabled then):
abstract sealed class SimpleModel<T>
permits SimpleModelSuccess, SimpleModelError {
#SerializedName("code")
final int code;
SimpleModel(final int code) {
this.code = code;
}
}
final class SimpleModelSuccess<T>
extends SimpleModel<T> {
#SerializedName("data")
final T data;
private SimpleModelSuccess(final int code, final T data) {
super(code);
this.data = data;
}
}
final class SimpleModelError<T>
extends SimpleModel<T> {
#SerializedName("data") // the annotation is helping here!
final String message;
private SimpleModelError(final int code, final String message) {
super(code);
this.message = message;
}
}
The code above can explain itself. Now the core part that required more work than I thought before by providing you my first comment that appeared incomplete.
#RequiredArgsConstructor(access = AccessLevel.PRIVATE)
final class SimpleModelTypeAdapterFactory
implements TypeAdapterFactory {
#Getter
private static final TypeAdapterFactory instance = new SimpleModelTypeAdapterFactory();
#Override
#Nullable
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
if ( !SimpleModel.class.isAssignableFrom(typeToken.getRawType()) ) {
return null;
}
// let's figure out what the model is parameterized with
final Type type = typeToken.getType();
final Type typeParameter;
if ( type instanceof ParameterizedType parameterizedType ) {
typeParameter = parameterizedType.getActualTypeArguments()[0];
} else {
throw new UnsupportedOperationException("Cannot infer type parameter from " + type);
}
// then borrow their respective type adapters for both success and error cases
#SuppressWarnings("unchecked")
final TypeAdapter<T> successDelegate = (TypeAdapter<T>) gson.getDelegateAdapter(this, TypeToken.getParameterized(SimpleModelSuccess.class, typeParameter));
#SuppressWarnings("unchecked")
final TypeAdapter<T> errorDelegate = (TypeAdapter<T>) gson.getDelegateAdapter(this, TypeToken.getParameterized(SimpleModelError.class, typeParameter));
return new TypeAdapter<>() {
#Override
public void write(final JsonWriter out, final T value) {
throw new UnsupportedOperationException();
}
#Override
public T read(final JsonReader in) {
// buffer the JSON tree first
// note that this solution may be very inefficient under some circumstances
final JsonObject buffer = Streams.parse(in).getAsJsonObject();
final JsonElement dataElement = buffer.get("data");
// is it's data is {...}, the consider it is success (by the way, what is code about?)
if ( dataElement.isJsonObject() ) {
return successDelegate.fromJsonTree(buffer);
}
// if it's a primitive, consider it's an error
if ( dataElement.isJsonPrimitive() ) {
return errorDelegate.fromJsonTree(buffer);
}
// well we've done our best...
throw new JsonParseException(String.format("Cannot deduce the model for %s", buffer.getClass()));
}
};
}
}
public final class SimpleModelTypeAdapterFactoryTest {
private static final class SomeJsonProvider
implements ArgumentsProvider {
#Override
public Stream<? extends Arguments> provideArguments(final ExtensionContext context) {
return Stream.of(
Arguments.of(
"""
{
"code": 2000,
"data": {
"id": 1,
"name": "example"
}
}
"""
),
Arguments.of(
"""
{
"code": 2001,
"data": "Error!"
}
"""
)
);
}
}
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#ToString
private static final class SimpleData {
private final String id;
private final String name;
}
private static final Type simpleModelOfSimpleDataType = TypeToken.getParameterized(SimpleModel.class, SimpleData.class)
.getType();
#ParameterizedTest
#ArgumentsSource(SomeJsonProvider.class)
public void test(final String json) {
final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.disableInnerClassSerialization()
.registerTypeAdapterFactory(SimpleModelTypeAdapterFactory.getInstance())
.create();
final SimpleModel<SimpleData> model = gson.fromJson(json, simpleModelOfSimpleDataType);
switch ( model ) {
case SimpleModelSuccess<SimpleData> success -> System.out.println(success.data);
case SimpleModelError<SimpleData> error -> System.out.println(error.message);
}
}
}
Here is what it prints to stdout:
SimpleModelTypeAdapterFactoryTest.SimpleData(id=1, name=example)
Error!
Well, yeah, this is a "bit" more tricky than it was suggested by my first comment.
I am trying to automate PUT request for our rest service in which I am passing a PUT body. I am trying to use HashMap to create an object for the body and add the values to it.
I am not sure how to add values using Hashmap for the nested JSON elements.
My body is something like this:
{
"versions": [
{
"versionname": "Test",
"number": 1
}
],
"id": 19,
"name": "TEST",
}
My code is as below:
public static Map<String, Object> map = new HashMap<String, Object>();
map.put("id", "2");
map.put("name", "TEST");
My question is how to add values for 'versionname' and 'number' element into the map so that I can pass that in my PUT request's body? Any help much appreciated.
Use strict typing, do not use maps if you know your payload (typed from the top of my head, may not immediately compile):
public class Request {
public static class Version {
public String versionname;
public int number;
protected Version() {} // for deserializer
public Version(String versionname, int number) {
this.versionname = versionname;
this.number = number;
}
}
public List<Version> versions = new ArrayList<>();
public int id;
public String name;
}
Request request = new Request();
request.versions.add(new Version("Test", 1));
request.id = 19;
request.name = "TEST";
String jsonString = new ObjectMapper().writeValueAsString(request);
Request deserialized = new ObjectMapper().readValue(jsonString, Request.class);
I'm using Jackson in Spring MVC application. I want to use a String value as key name for Java POJO --> JSON
"record": {
"<Dynamic record name String>": {
"value": {
....
}
}
}
So the dynamic record name String could be "abcd","xyz" or any other string value. How can I define my "record" POJO to have a key like that ?
Unfortunately, you cannot have dynamic fields in Java classes (unlike some other languages), so you have two choices:
Using Maps
Using JSON objects (i.e. JsonNode in case of Jackson)
Suppose, you have a data like this:
{
"record": {
"jon-skeet": {
"name": "Jon Skeet",
"rep": 982706
},
"darin-dimitrov": {
"name": "Darin Dimitrov",
"rep": 762173
},
"novice-user": {
"name": "Novice User",
"rep": 766
}
}
}
Create two classes to capture it, one for user and another for the object itself:
User.java:
public class User {
private String name;
private Long rep;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Long getRep() { return rep; }
public void setRep(Long rep) { this.rep = rep; }
#Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", rep=" + rep +
'}';
}
}
Data.java:
public class Data {
private Map<String, User> record;
public Map<String, User> getRecord() { return record; }
public void setRecord(Map<String, User> record) { this.record = record; }
#Override
public String toString() {
return "Data{" +
"record=" + record +
'}';
}
}
Now, parse the JSON (I assume there is a data.json file in the root of your classpath):
public class App {
public static void main(String[] args) throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();
System.out.println(objectMapper.readValue(App.class.getResourceAsStream("/data.json"), Data.class));
System.out.println(objectMapper.readTree(App.class.getResourceAsStream("/data.json")));
}
}
This will output:
Data{record={jon-skeet=User{name='Jon Skeet', rep=982706}, darin-dimitrov=User{name='Darin Dimitrov', rep=762173}, novice-user=User{name='Novice User', rep=766}}}
{"record":{"jon-skeet":{"name":"Jon Skeet","rep":982706},"darin-dimitrov":{"name":"Darin Dimitrov","rep":762173},"novice-user":{"name":"Novice User","rep":766}}}
In case of a Map you can use some static classes, like User in this case, or go completely dynamic by using Maps of Maps (Map<String, Map<String, ...>>. However, if you find yourself using too much maps, consider switching to JsonNodes. Basically, they are the same as Map and "invented" specifically for highly dynamic data. Though, you'll have some hard time working with them later...
Take a look at a complete example, I've prepared for you here.
This is in Kotlin but I have found a solution to the same problem using Jackson.
You don't need the root node "record", so you will need to get rid of it or start one node deeper(you're on your own there) but to turn the list of records that are children of their id into a list of records with id in the object follows:
val node = ObjectMapper().reader().readTree(json)
val recordList = mutableListOf<Record>()
node.fields().iterator().forEach {
val record = record(
it.key,
it.value.get("name").asText(),
it.value.get("rep").asText()
)
recordList.add(event)
}
node.fields() returns a map of children(also maps)
iterating through the parent map you will get the id from the key and then the nested data is in the value (which is another map)
each child of fields is key : value where
key = record id
value = nested data (map)
This solution, you don't need multiple classes to deserialize a list of classes.
I have my data in this format:
{
"0" : {"a": {}}, {"b": {}}, ...
"1" : {"c": {}}, {"d": {}}, ...
.
.
.
}
I am able to capture it into a map using the dynamic capture feature of jackson by using #JsonAnySetter annotation.
public class Destination{
Map<String, Object> destination = new LinkedHashMap<>();
#JsonAnySetter
void setDestination(String key, Object value) {
destination.put(key, value);
}
}
I am trying to parse a json string to java object but i am not sure on the object hierarchy.
below is the json string
{
"TABLE_Detail":{
"1":{
"TABLE":"table1",
"RUN_DATE":"20170313",
"SEQ_NUM":"123",
"START_TIME":"20170313133144",
"END_TIME":"20170313133655"
},
"2":{
"TABLE":"table2",
"RUN_DATE":"20170313",
"SEQ_NUM":"123",
"START_TIME":"20170313133142",
"END_TIME":"20170313133723"
}
}
}
Here the number 1, 2 are dynamic and can go up to any number, I tried to create a outer object and have a Map of type key String and value as object TableData. The map having variable name TABLE_Detail. but the TableData object is always null. TableData object has all the variables.
Please help me on how to convert this json string to object.
Change 1 to table1 and 2 to table2:
public class TableDetails {
private TableData table1;
private TableData table2;
public TableDetails(){
}
// getter and setter
}
And if modify json format to "Koen Van Looveren" mentioned:
public class TableDetails {
List<TableData> tables;
public TableDetails() {
}
// getter and setter
}
The table class:
Table.java:
public class TableData {
private String table;
private String run_date;
private String seq_num;
private String start_time;
private String end_time;
public TableData() {
}
// getter and setter
}
you have two choice for such painfully json structure when using Gson.
using Gson parsing json as Map and write some class access returned Map.this mode works fine for access data only!
//usage
TableDetails details=new TableDetails(gson.fromJson(json, Map.class));
//classes
class TableDetails {
private Map<String, Map> context;
public TableDetails(Map root) {
this.context = (Map<String, Map>) root.get("TABLE_Detail");
}
public int size() {
return context.size();
}
public Table get(String key) {
return new Table(context.get(key));
}
}
class Table {
private Map context;
public Table(Map context) {
this.context = context;
}
public String getName() {
return get("TABLE");
}
private <T> T get(String name) {
return (T) context.get(name);
}
...
}
write your own Gson TypeAdapter,but this way may be more complex.if you interesting on write custom TypeAdapter there is a demo that I written for extract json root.gson-enclosing-plugin
You can try deserialize it into a Map<String, Map<String, TableData>>. The reason why Map<String, TableData> doesn't work it that the pesudo-array is wrapped in another object.
The following example converts a response into a List<TableData>:
public List<TableData> deserialize(String json) {
return Gson().<Map<String, Map<String, TableData>>>fromJson(json, new TypeToken<Map<String, Map<String, TableData>>>(){}.getType())
.values().iterator().next()
.entrySet().stream()
.sorted(Comparator.comparingInt(e -> Integer.parseInt(e.getKey())))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
I was in a search for the solution, and i came across one of the site where the solution worked. i wanted to credit the below site. Thanks for all the support.
I am able to map the dynamic value 1, 2 as map keys and values are mapped correspondingly to the TableData object properties using #SerializedName gson annootation.
http://findnerd.com/list/view/Parse-Json-Object-with-dynamic-keys-using-Gson-/24094/
When using an array in json you need to use [ for opening and ] for closing
{
"TABLE_Detail": [
{
"TABLE": "table1",
"RUN_DATE": "20170313",
"SEQ_NUM": "123",
"START_TIME": "20170313133144",
"END_TIME": "20170313133655"
},
{
"TABLE": "table2",
"RUN_DATE": "20170313",
"SEQ_NUM": "123",
"START_TIME": "20170313133142",
"END_TIME": "20170313133723"
}
]
}
I have the following Item class:
public class Item {
public Object item;
}
I am inserting a JSON into this object using GSON.
tmp =
{
"_id": {
"$oid": "5076371389d22e8906000000"
},
"item": {
"values": [
{
"value1": [
4958,
3787,
344
],
"value2": [
4,
13,
23
]
}
],
"name": "item1"
}
}
Java bit:
Item item = new Item();
Gson g = new Gson();
it = g.fromJson(tmp.toString(), Item.class);
it.item becomes a StringMap type (http://code.google.com/p/google-gson/source/browse/trunk/gson/src/main/java/com/google/gson/internal/StringMap.java?r=1131)
I now need to access the sub-objects within this object.
I can use the overridden toString function this type has which prints all objects within this object. But how would I be able to navigate through it?
P.S. The reason I put everything into an object datatype not a structured class is that the JSON structure varies each time, so I can't really have a class schema.
Any suggestions?
You should create an object structure that reflects the JSON instead (since this is what you're trying to do anyway). For your example, you could use this:
public class MyObject {
private Item item;
private String _id;
// getters, setters, etc.
}
public class Item {
private List<Value> values;
private String name;
// getters, setters, etc.
}
public class Value {
private List<Integer> values1;
private List<Integer> values2;
// getters, setters, etc.
}
Then pass MyObject.class to Gson:
MyObject myObj = g.fromJson(tmp.toString(), MyObject.class);
You can get the lists in values like so:
List<Integer> values1 = myObj.getItem().getValues().get(0).getValues1();
List<Integer> values2 = myObj.getItem().getValues().get(0).getValues2();
Try that and see if it works.
Also, you should check out my answer to a similar question here, specifically the part at the end about how to write an object structure for Gson based on some JSON object.
You can always create a constructor for the custom object that uses reflection and takes the StringMap
public MyObject(StringMap sm){
Iterator it = sm.entrySet().iterator();
while(it.hasNext()){
Entry pairs = (Entry)it.next();
Class<?> c = this.getClass();
try {
Field value = c.getDeclaredField((String) pairs.getKey());
value.set(this, pairs.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}