Json Java Data Binding for multiple POJOs - jackson

Is it possible to parse a JSON containing multiple Objects with the ObjectMapper? For example
{
"employee": {
"name": "John",
"surname": "Smith",
"age": 30,
"department": "sales"
},
"department": {
"name": "sales",
"company": "abcd",
"lead": "Mr Harrison"
},
"company": {
"name": "abcd",
"location": "New York"
}
}
Can I get the objects Employee, Department, Company out of that file in one single mapper run, something like:
ObjectMapper mapper = new ObjectMapper();
List of Objects = mapper.readValue(...)
Or is it not possible?

Make a parent object that contains the 3 objects you are looking for, and read them into that single object, then use that object to access your data.

If we consider the case of
reading numerous objects in one file separately
, without creating a dedicated wrapper POJO, this is also possible to achieve, provided that you have the information to which target object type should be mapped each of the root-level keys in the JSON.
This information can be represented in a Map:
Map<String, Class<?>> targetTypes = new HashMap<>();
targetTypes.put("employee", Employee.class);
targetTypes.put("department", Department.class);
targetTypes.put("company", Company.class);
The deserialization will have to be done in two steps. The first one is to transform the original JSON into a Map<String, Object>:
String json = ... // the JSON
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> parsed = mapper.readValue(json, Map.class);
The second step is to match the keys of this map with the target types and transform the values into objects:
List<Object> objects = parsed.entrySet().stream().map(
(entry) -> {
Class<?> targetClass = targetTypes.get(entry.getKey());
return mapper.convertValue(entry.getValue(), targetClass);
}
).collect(Collectors.toList());
The objects list now contains
[
Employee(name=John, surname=Smith, age=30, department=sales),
Department(name=sales, company=abcd, lead=Mr Harrison),
Company(name=abcd, location=New York)
]

Related

Problem to deserialize json to java object of generic type with jackson 2.13.4.2

There's a message defined as below, note there're different implementations of the generic type M
// The message definition
#Value
#Builder(toBuilder = true)
#Jacksonized
public class MyMessage<M> {
#Builder.Default
Map<String, String> props = new HashMap<>();
M content;
}
// One implementation of the generic type M in MyMessage
class ContentType1 {
String name;
SomeSimplePojo pojo;
Map<String, String> contentProps;
}
Here's an example of above message:
{
"props": {
"trace-id": "3468f6022b749dbc"
},
"content": {
"name": "contentExample1",
"pojo": {
"field1": "val1",
"field2": "val2"
},
"contentProps": {
"/Count": "9",
"/Email": "someone#stackoverflow.com"
}
}
}
The message was deserialized basically with below code snippet, by com.fasterxml.jackson.* version 2.10.4. It worked fine before.
ObjectMapper objectMapper = new ObjectMapper();
MyMessage<String> rawMsg = objectMapper.readValue(jsonStr, new TypeReference<MyMessage<String>>() {});
// Here clazzOfM is the class of type M
MyMessage<M> convertedMsg = MyMessage.<M>builder().content(objectMapper.convertValue(rawMsg.getContent(), clazzOfM)).props(rawMsg.getProps()).build();
But recently, I upgraded com.fasterxml.jackson.databind to 2.13.4.2, all other com.fasterxml.jackson.* to 2.13.4. Then it failed at this line MyMessage<String> rawMsg = objectMapper.readValue(jsonStr, new TypeReference<MyMessage<String>>() {}); with Exception, which points to the generic type M, field content at column 52:
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
at [Source: (String)"{"props":{"trace-id":"3468f6022b749dbc"},"content":{"name":"contentExample1","pojo":{"field1":"val1","field2":"val2"},"contentProps":{"/Count":"9","/Email":"someone#stackoverflow.com"}}}"; line: 1, column: 52] (through reference chain: com.demo.example.MyMessage$MyMessageBuilder["content"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1741)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1515)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1420)
at com.fasterxml.jackson.databind.DeserializationContext.extractScalarFromObject(DeserializationContext.java:932)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:62)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeSetAndReturn(MethodProperty.java:158)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.vanillaDeserialize(BuilderBasedDeserializer.java:293)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserialize(BuilderBasedDeserializer.java:217)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3612)
I tried to change the problematic line to MyMessage<M> rawMsg = objectMapper.readValue(jsonStr, new TypeReference<MyMessage<M>>() {});, no exception then, but I still have two questions.
Is the above change a correct approach to deserialize json text to MyMessage by jackson 2.13.4(.2)? What is the best practice to this kind of deserialization if the above isn't.
After the above change, I notice that the type of rawMsg.content is LinkedHashMap, it isn't M(CotentType1 in this test) as I expected. But the type of convertedMsg.content IS ContentType1 after executing this converting line MyMessage<M> convertedMsg = MyMessage.<M>builder().content(objectMapper.convertValue(rawMsg.getContent(), clazzOfM)).props(rawMsg.getProps()).build();.
I can't understand why the type of rawMsg.content is LinkedHashMap instead of ContentType1. Could someone help explain?

How to make api call of string in flutter?

I am learning Flutter. This is the string that I need to call and I don't know how to call this type of string.
{
"Info":[
{
"c_type_id":"1",
"cleaning type":"Washroom Cleaning"
},
{
"c_type_id":"2",
"cleaning type":"Garden\/Lawn Cleaning"
}
]
}
My code
class Album {
final String title;
Album({
this.title,
});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
title: json['title'],
);
}
}
As I am following my code like this https://flutter.dev/docs/cookbook/networking/fetch-data
and got this error "A non-null String must be provided to a Text widget." because they are following this type of string and my string type is different. Help!
{
"userId": 1,
"id": 1,
"title": "quidem molestiae enim"
}
Your given data (API response) seems to have a list of maps, so you should get the data first (use async function):
var response = await http.get("Your Url")
then you should extract the given map list as follow:
var temp = json.decode(response);
List<dynamic> result = temp['Info']); // it is the described list
now, you can use your data (extract each parameter):
String c_type_id = result[0]['c_type_id'];
// and so on...
Your question is not clear at all but I can show you an example how to reach an element in a json.
Your json consists a list that contains string keys. To reach first element of list;
json["Info"][0]
Then pass the key you want to get its value;
json["Info"][0]["c_type_id"]
http_requests All type of http requests are mentioned in this post. If you have any further problem kindly make comment.

ASPNET Core Options Binding does not populate dictionary key/value pair

I have the following JSON configuration
"Configurations": {
"KeyA": {
"Ids": []
},
"KeyB": {
"Ids": [1, 2, 3]
},
"KeyC": {
"Ids": [1, 2, 3],
"OptionalData": "asdf"
}
}
This is then read into the following object
public class AppConfiguration
{
public Dictionary<ConfigType, ConfigurationData> Configurations {get; set;} = new Dictionary<ConfigType, ConfigurationData>();
}
public class ConfigurationData
{
public HashSet<int> Ids {get;set;} = new HashSet<int>();
public string OptionalData = "";
}
public Enum ConfigType
{
KeyA = 1,
KeyB = 2,
KeyC = 3
}
I then bind this in ConfigureServices(IServiceCollection services) method using
services.Configure<AppConfiguration>(this.Configuration);
However, I notied that the configuration binding produces my AppConfiguration's dictionary with only the KeyB and KeyC keys, skipping KeyA, because its Ids array is empty. I read up on the behaviour of the configuration binding online, but as far as I saw it should bind the Ids to null, but here it just does not generate a key value pair in the dictionary altogether.
I tried removing the "Ids" property, leaving my config like "KeyA": {}", but this still did not work. The only way I can get it to parse is if I put in some numbers in the array, but this obviously not what I want.
I would like to know if there is anyway I can bind such a key-value pair, where I don't have any Ids in my array. This seems like it should be somehow supported out of the box, but I'm not sure why it's not working and how could I resolve it, without implementing some hacky custom configuration loader/binder.
For anyone who stumbles upon this in the future, I managed to solve this by setting my array to null, instead of an empty array like so.
"Configurations": {
"KeyA": {
"Ids": null
}
}

How to change JSON returned by query using Helidon 2.0.0-M-2

I'm using Helidon 2.0.0-M2.
When I run the query below I get back a list of JSON objects.
dbClient.execute(exec -> exec.createNamedQuery("select-dsitem-by-id")
.addParam("userId", dataItemId)
.execute())
.thenAccept(response::send)
.exceptionally(throwable -> sendError(throwable, response));
Returned list
[
{
"data": "qwerty",
"user_id": "12345"
},
{
"data": "qwerty123",
"user_id": "22345"
}
]
The attribute names seem to be taken directly from the database column name. e.g. one attribute name returned is "user_id". However, I want it to be "userId". I also want to create a parent wrapper for this list like:
{
"userList": [
{
"data": "qwerty",
"user_id": "12345"
},
{
"data": "qwerty123",
"user_id": "22345"
}
]
}
What is the best way to do this with the dbclient?
Thanks
Simple approach:
Change your SQL statement to return the correct name, such as:
SELECT data, user_id as userId FROM mytable
Complicated approach:
We are working on a better support to map to a JSON stream.
Currently there is only one (a bit complicated) way to achieve this:
You can create a custom mapper from a DbRow to JsonObject. This mapper needs to be a general one (it must work for any DbRow of any query).
The built-in mapper uses metadata provided on the columns. I have prepared a simple example (that just expects to have a single type of statements):
class DbRecordMapperProvider implements DbMapperProvider {
private static final DbMapper<JsonObject> MAPPER = new DbRecordMapper();
#SuppressWarnings("unchecked")
#Override
public <T> Optional<DbMapper<T>> mapper(Class<T> aClass) {
if (JsonObject.class.equals(aClass)) {
return Optional.of((DbMapper<T>)MAPPER);
}
return Optional.empty();
}
}
class DbRecordMapper implements DbMapper<JsonObject> {
#Override
public JsonObject read(DbRow dbRow) {
return Json.createObjectBuilder()
.add("name", dbRow.column("FIRSTPART").as(String.class))
.add("message", dbRow.column("SECONDPART").as(String.class))
.build();
}
#Override
public Map<String, ?> toNamedParameters(JsonObject dbRecord) {
return dbRecord;
}
#Override
public List<?> toIndexedParameters(JsonObject dbRecord) {
throw new IllegalStateException("Cannot convert json object to indexed parameters");
}
}
The important method is public JsonObject read(DbRow dbRow).
Once you have such a DbMapperProvider, you register it with the DbClient:
dbClient = DbClient.builder()
.config(config.get("db"))
.mapperProvider(new DbRecordMapperProvider())
.build();

Cannot deserialize the current JSON array (e.g. 1 2 3 ) into type

I'm getting an error when I try to deserialize a JSON string:
Cannot deserialize the current JSON array (e.g. [1,2,3]) ...
vb.net code:
Public Class DATA
Public Property CPU As String
End Class
Dim data As DATA = JsonConvert.DeserializeObject(Of DATA)(File.ReadAllText("laptop.json"))
TextBox25.Text = DATA.CPU
The JSON file:
[
{
"spec": "CPU";
"value": "Intel Core i3-4005U"
};
{
"spec": "Speed";
"value": "1.7 GHz"
};
{
"spec": "Cache";
"value": "3MB"
};
{
"spec": "RAM";
"value": "4GB"
};
{
"spec": "Warranty Period";
"value": "1 Year"
}
]
How can I fix this error?
There are three problems here.
First, the JSON as you have shown it is invalid. Properties within JSON objects and values within JSON arrays must be separated by commas (,), not semicolons (;). See JSON.org.
Second, your JSON represents an array (or list) of items, but you are trying to deserialize into a single class. You need to deserialize into a list like this:
Dim json As String = File.ReadAllText("laptop.json")
Dim list As List(Of DATA) = JsonConvert.DeserializeObject(Of List(Of DATA))(json)
Third, the property names in the DATA class you are using do not match the JSON property names of the objects in the array. The class should be defined like this instead:
Class DATA
Public Property spec As String
Public Property value As String
End Class
Fiddle: https://dotnetfiddle.net/0aI2C3