I'm working on creating an API that has nested lists. Jackson seems like a great tool to create objects, but I can't quite figure out how to nest a list, and I'm wondering if its possible.
My object looks like this.
public class Order {
public String name;
public List<Item> items;
}
I'm hoping there is a way to map it to json that looks something like:
{
name : "A name"
items : {
elements : [{
price : 30
}]
}
}
We want to be able to do this so we can add properties to lists.
You can write custom deserializer for List<Item> items. See below example:
class ItemsJsonDeserializer extends JsonDeserializer<List<Item>> {
#Override
public List<Item> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
InnerItems innerItems = jp.readValueAs(InnerItems.class);
return innerItems.elements;
}
private static class InnerItems {
public List<Item> elements;
}
}
Now, you have to inform Jackson to use it for your property. You can do this in this way:
public class Order {
public String name;
#JsonDeserialize(using = ItemsJsonDeserializer.class)
public List<Item> items;
}
In general it is best to map JSON structure exactly to Java. In your case you could use something like:
public class Order {
public String name;
public ItemList items;
}
public class ItemList {
public List<Item> elements;
// and any properties you might want...
}
alternatively, you could probably also use (relatively) new #JsonFormat annotation:
public class Order {
public String name;
public ItemList items;
}
// annotation can be used on propery or class
#JsonFormat(shape=Shape.OBJECT) // instead of Shape.ARRAY
public class ItemList extends ArrayList<Item>
{
public Iterator<Item> getElements() { return this.iterator(); }
public String getSomeAttribute() { ... }
}
where you are forcing List or Collection to be serialized as if it was POJO, instead of normal special handling. There may be some side-effects, since introspection is used to find possible accessors, but the general approach should work
Your JSON translates to: "the object named items is of a type that has a property named elements which is a list of some sort".
So your Item class just needs an elements property:
class Item {
List<Something> getElements();
}
Note that your Java code doesn't map to your JSON. Your Java classes would map to something like:
{
"name" : "foo",
"items" : [
{ /* encoded version of Item */ }
]
}
For scala, one can try:
class ItemsJsonDeserializer extends JsonDeserializer[List[Item]] {
val mapper = new ObjectMapper() with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
override def deserialize(jp: JsonParser, dc: DeserializationContext): List[Item] = {
val oc = jp.getCodec
val nodes = oc.readTree[ObjectNode](jp).get("elements").asScala.toList
val res = nodes.map { node =>
mapper.readValue[Item](node.toString)
}
res
}
}
case class Item(price: Int)
case class Order {
name: String,
#JsonDeserialize(using = classOf[ItemsJsonDeserializer])
items: List<Item>
}
Related
I have the following JSON
{
"ads": [
{
"228029_228029": {
"ad_id": "228029",
"duration": 10,
"m3u8_text": {
"_1280p": "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-ALLOW-CACHE:YES\n#EXT-X-TARGETDURATION:7\n#EXT-X-MEDIA-SEQUENCE:0\n#EXTINF:7.120000,\n_1280p_0000.ts\n#EXTINF:2.880000,\n_1280p_0001.ts\n#EXT-X-ENDLIST\n",
"_320p": "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-ALLOW-CACHE:YES\n#EXT-X-TARGETDURATION:7\n#EXT-X-MEDIA-SEQUENCE:0\n#EXTINF:7.120000,\n_320p_0000.ts\n#EXTINF:2.880000,\n_320p_0001.ts\n#EXT-X-ENDLIST\n"
}
}
},
{
"228845_228845": {
"ad_id": "228845",
"duration": 24,
"m3u8_text": {
"_1280p": "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-ALLOW-CACHE:YES\n#EXT-X-TARGETDURATION:8\n#EXT-X-MEDIA-SEQUENCE:0\n#EXTINF:7.840000,\n_1280p_0000.ts\n#EXTINF:6.880000,\n_1280p_0001.ts\n#EXTINF:6.680000,\n_1280p_0002.ts\n#EXTINF:2.600000,\n_1280p_0003.ts\n#EXT-X-ENDLIST\n",
"_320p": "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-ALLOW-CACHE:YES\n#EXT-X-TARGETDURATION:8\n#EXT-X-MEDIA-SEQUENCE:0\n#EXTINF:7.840000,\n_320p_0000.ts\n#EXTINF:6.880000,\n_320p_0001.ts\n#EXTINF:6.680000,\n_320p_0002.ts\n#EXTINF:2.600000,\n_320p_0003.ts\n#EXT-X-ENDLIST\n"
}
}
}
],
"total_duration": 80
}
I have created the respective model class as
This is the root model
#JsonIgnoreProperties(ignoreUnknown = true)
public class AdsResponse {
#JsonProperty("ads")
List<Ad> ads;
#JsonProperty("total_duration")
long totalDuration;
}
Then the ads model
public class Ad {
Map<String,AdInfo> ad;}
Then the AdInfo model
public class AdInfo {
#JsonProperty("m3u8_text")
AdManifest adManifest;
int duration;
#JsonProperty("ad_id")
String adId;}
Then the manifest model
public class AdManifest {
#JsonProperty("_1280p")
String _1280p;
#JsonProperty("_320p")
String _320p;}
When I try to parse this using below code
AdsResponse response = new ObjectMapper().readValue(
res,
AdsResponse.class);
I get the empy ad object
AdsResponse{ads=[Ad{ad=null}, Ad{ad=null}, totalDuration=80}
What is wrong here?
You don't actually need the Ad class, it's just a Map<>. AdResponse can look like this:
public class AdsResponse {
#JsonProperty("ads")
List<Map<String, AdInfo>> ads;
#JsonProperty("total_duration")
long totalDuration;
If the keys in the map were predictable, you could make them properties on the Ad class and then Jackson would map them properly. But since they're not (they look like some kind of ID), mapping them to a Map<> is probably the best option.
As an alternative, if you want or need to have the Ad objects, you can map them like this:
public class Ad {
Map<String, AdInfo> adInfo = new HashMap<>();
#JsonAnySetter
public void setAds(String key, AdInfo value) {
adInfo.put(key, value);
}
}
With that, and AdResponse defined the way you have it in the question, you'll get populated Ad instances, each of which has a Map<> with only 1 key/value pair. For an even simpler (and probably more sensible model, you can eliminate the Map if there is only ever 1 key in an Ad, like this:
public class Ad {
private AdInfo adInfo;
#JsonAnySetter
public void setAdInfo(String ignored, AdInfo value) {
this.adInfo = value;
}
}
I'm new with java and objectMapper. I'm trying to parse json field that is possible that a key have two types, it could be a string or array.
examples:
{
"addresses": [],
"full_name": [
"test name_1",
"test name_2"
],
}
or
{
{
"addresses": [],
"full_name": "test name_3",
}
}
Class example:
#JsonIgnoreProperties(ignoreUnknown = true)
#Data -> lombok.Data
public class Document {
private List<String> addresses;
#JsonProperty("full_name")
private String fullName;
}
I used objectMapper to deserialize json, works correctly when the 'full_name' field has a string but when arrive an array fail deserialization.
The idea is that when arrive a string put value in attribute but when arrive array, concatenate de array elements as string (String.join(",", value))
It's possible to apply custom deserialization in a class method? For example setFullName() (use lombok.Data)
I saw others examples in this site, but not work.
Thank's for all
From jackson 2.6 you can use JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY
#JsonProperty("full_name")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private String[] fullName;
Elaborating on #Deadpool answer, you can use setter which accept the array and then join it to string:
#JsonProperty("full_name")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
void setFullName(String[] name)
{
this.fullName = String.join(",", name);
}
Both answers are great. I just want to mention about custom Deserializer.
You can easily extend from StdDeserializer<Document> and override deserialize method:
public class DocumentDeserializer extends StdDeserializer<Document> {
#Override
public Document deserialize(JsonParser p, DeserializationContext ctxt, Document value) throws IOException {
JsonNode root = p.getCodec().readTree(p);
JsonNode node = root.get("full_name");
if(node.isArray()) {
//get array data from node iterator then join as String and
//call setFirstName
}
return value;
}
}
Then don't forget to call registerModule of ObjectMapper to register your deserializer
I have a list of objects populated and I need to populate more attributes from a Object.
Considering using mapperstruct and I can't modifying previous atributes of Objects from a target list.
// type for obj in list target
public class ObjectExampleA {
private String id;
private String firstAttr;
private String secondAttr;
private String thirdAttr;
}
// target list
// List<ObjectExampleA> listTarget;
// at this point considering an elements in list like this...
// {"id":1, "firstAttr":"blue", "secondAttr":"black", "thirdAttr": null}
// {"id":2, "firstAttr":"gray", "secondAttr":"red", "thirdAttr": null}
// source obj
public class ObjectExampleB {
private String thirdAttr;
}
// I need to populate all thirdAttr atribute of all elements in listTarget from ObjectExampleB.thirdAttr value
#Mapper(componentModel = "spring")
public interface ExampleMapper {
void populateThirdAttrInList(#MappingTarget List<ObjectExampleA> listTarget, ObjectExampleB objectExampleB);
}
Performing an update of elements of a collection is not always trivial. However, your case can be solved inn a simple way by doing an update of ObjectExampleA.
E.g.
#Mapper(componentModel = "spring")
public interface ExampleMapper {
default void populateThirdAttrInList(#MappingTarget List<ObjectExampleA> listTarget, ObjectExampleB objectExampleB) {
for(ObjectExampleA exampleA: listTarget) {
populateThirdAttr(exampleA, objectExampleB);
}
}
void populateThirdAttr(#MappingTarget ObjectExampleA exampleA, ObjectExampleB exampleB);
}
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 converted a DOM document to json String. However, there are some issues with the way List is mapped in scenario where the List has only one value and List has multiple values.
For ex:
1) After DOM document has been convered to json string, here AlphaStatus List has only with one value:
{
"Gamma": {
.
.
.
.
"AlphaStatuses": {
"AlphaStatus": {
"AlphaHeaderKey": "201612221122273660",
"AlphaLineKey": "201612221122273661",
}
},
"Delta": {
...
}
}
}
2) After DOM document has been convered to json string, here AlphaStatus List has only with multiple values is shown as:
{
"Gamma": {
.
.
.
.
"AlphaStatuses": {
"AlphaStatus": [
{
"AlphaHeaderKey": "201612221122273660",
"AlphaLineKey": "201612221122273661",
},
{
"AlphaHeaderKey": "201612221122273660",
"AlphaLineKey": "201612221122273662",
},
{
"AlphaHeaderKey": "201612221122273660",
"AlphaLineKey": "2016}2221122273663",
}
]
},
"Delta": {
...
}
}
}
I am using the below jackson code to convert xml string to json:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
Object json = mapper.readValue(jObject.toString(), Object.class);
String output = mapper.writeValueAsString(json);
My question is, how do i ensure that AlphaStatus List is always starting with [{ and ending with }], no matter whether it has only one value or multiple values. How can this be resolved.
It is causing issues in the other system which assumes that AlphaStatus is a List always and expects [{ to be part of the token.
Any help is appreciated.? Or should i use some string utility in such cases to parse AlphaStatus and replace with [{ and }]. How can this be done
First, it seems the line
Object json = mapper.readValue(jObject.toString(), Object.class);
is useless, because you already have an object (jObject) to serialize.
Just use it:
String output = mapper.writeValueAsString(jObject);
For second, it seems your problematic field is of type java.lang.Object, right?
If you as assign a single value to it, it will result in one single Json object:
jObject.setAlphaStatuses(alphaStatus); -> result -> {...}
If you as assign some kind of collection, it will result in a Json array:
jObject.setAlphaStatuses(Arrays.asList(alphaStatus1, alphaStatus2)); -> result -> [{...},{...}]
To avoid that, either always pass a list or (if you can change the definition of the class) make it to a Collection (maybe some List).
Here a small snippet to test:
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonObjects {
private final static ObjectMapper mapper = new ObjectMapper();
private final static AlphaStatus as1 = new AlphaStatus();
private final static AlphaStatus as2 = new AlphaStatus();
static {
as1.setAlphaHeaderKey("A");
as1.setAlphaLineKey("B");
as2.setAlphaHeaderKey("C");
as2.setAlphaLineKey("D");
}
public static void main(String[] args) throws JsonProcessingException {
final Gamma gamma = new Gamma();
gamma.setAlphaStatuses(Arrays.asList(as1, as2));
System.out.println(mapper.writeValueAsString(gamma));
gamma.setAlphaStatuses(as1);
System.out.println(mapper.writeValueAsString(gamma));
}
static class Gamma {
Object alphaStatuses;
public Object getAlphaStatuses() {
return alphaStatuses;
}
public void setAlphaStatuses(Object alphaStatuses) {
this.alphaStatuses = alphaStatuses;
}
}
static class AlphaStatus {
String alphaHeaderKey;
String alphaLineKey;
public String getAlphaHeaderKey() {
return alphaHeaderKey;
}
public void setAlphaHeaderKey(String alphaHeaderKey) {
this.alphaHeaderKey = alphaHeaderKey;
}
public String getAlphaLineKey() {
return alphaLineKey;
}
public void setAlphaLineKey(String alphaLineKey) {
this.alphaLineKey = alphaLineKey;
}
}
}
And the result (not exactly your result, only for demonstration):
{"alphaStatuses":[{"alphaHeaderKey":"A","alphaLineKey":"B"},{"alphaHeaderKey":"C","alphaLineKey":"D"}]}
{"alphaStatuses":{"alphaHeaderKey":"A","alphaLineKey":"B"}}
#JsonRootName("Gamma")
public class Gamma {
private AlphaStatuses AlphaStatuses;
// getters and setters
}
public class AlphaStatuses {
#JsonProperty("alphaStatus")
private List<AlphaStatus> alphaStatuses;
// getters and setters
}
public class AlphaStatus{
#JsonProperty("alphaHeaderKey")
private String alphaHeaderKey;
#JsonProperty("alphaLineKey")
private String alphaLineKey;
// getters and setters
}
**Test class**:
#Test
public void test() throws Exception {
Gamma gamma=new Gamma();
gamma.setAlphaStatuses(new AlphaStatuses(Arrays.asList(new AlphaStatus("201612221122273660","201612221122273660"))));
ObjectMapper mapper=new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE,true);
String jsonString=mapper.writeValueAsString(gamma);
System.out.println("output "+jsonString);
}
**Output**:
output {"Gamma":{"alphaStatues":{"alphaStatus":[{"alphaHeaderKey":"201612221122273660","alphaLineKey":"201612221122273660"}]}}}