3

Im converting a json to a java object but I'm not getting what I want. I have duplicated keys "friend" in my json.

My json:

{
    "id" : "5ee2e2f780bc8e7511a65de9",
    "friends": [{
        "friend": {
            "id": 1,
            "name": "Priscilla Lynch"
        },
        "friend": {
            "id": 2,
            "name": "William Lawrence"
        }
    }]
}

Using readValue from ObjectMapper only takes the last one "friend" but I need both. I know JSONObject use Map to convert so that's why it is taking the last one.

Result: Contacts(id=5ee2e2f780bc8e7511a65de9, friends=[{friend={id=2, name=William Lawrence}}])

ObjectMapper mapper = new ObjectMapper();
Contacts contacts = mapper.readValue(json, Contacts.class);

Contacts Pojo:

@Getter
@Setter
@ToString
public class Contacts {

    String id;
    List<Object> friends;
}

I want a list of all friends. As the service whose provide the json is not in my hand I need to find a way to solve it. I tried use MultiMap from apache.commons but no success. Im stuck on this.

user276608
  • 33
  • 4
  • 1
    You need `Friend` as object? Cause you can extract them from `JsonNode` without deserialize them. – KunLun Jun 12 '20 at 22:13
  • 1
    Yes, I need a list of Friend – user276608 Jun 12 '20 at 22:16
  • Your json is somehow weird. You have 2 fields with same name `friend` in same node. Which make Jackson to pick just one of them(last one). So even if you handle it with JsonNode, first `friend` is missing. You can execute this `JsonNode jsonNode = mapper.readTree(json); System.out.println(jsonNode.get("friends").get(0).toString());` It prints the first element of array `friends`. And it is printed just one, instead of both. In my opinion json is "wrong". Your `friends` is like this `[ { {}, {} } ]` – KunLun Jun 12 '20 at 22:49
  • @KunLun, `JSON` payload is valid. An object structure is represented as a pair of curly bracket tokens surrounding zero or more name/value pairs. A name is a string. A single colon token follows each name, separating the name from the value. A single comma token separates a value from a following name. The JSON syntax does not impose any restrictions on the strings used as names, does not require that name strings be unique, and does not assign any significance to the ordering of name/value pairs. [Source](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) – Michał Ziober Jun 12 '20 at 23:44

2 Answers2

2

When you have a JSON Object with duplicated fields you can use com.fasterxml.jackson.annotation.JsonAnySetter annotation. Result will be a List<List<X>> so, you can use flatMap method to create List<X>. See below example:

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class DuplicatedFieldsInJsonObjectApp {

    public static void main(String[] args) throws Exception {
        File jsonFile = new File("./resource/test.json").getAbsoluteFile();

        ObjectMapper mapper = JsonMapper.builder().build();
        Contacts contacts = mapper.readValue(jsonFile, Contacts.class);
        System.out.println(contacts.getUnwrappedFriends());
    }
}

@Getter
@Setter
@ToString
class Contacts {

    String id;
    List<Friends> friends;

    public List<Friend> getUnwrappedFriends() {
        return friends.stream().flatMap(f -> f.getFriends().stream()).collect(Collectors.toList());
    }
}

class Friends {

    private List<Friend> friends = new ArrayList<>();

    @JsonAnySetter
    public void setAny(String property, Friend friend) {
        friends.add(friend);
    }

    public List<Friend> getFriends() {
        return friends;
    }
}

@Getter
@Setter
@ToString
class Friend {
    int id;
    String name;
}

Above code prints:

[Friend(id=1, name=Priscilla Lynch), Friend(id=2, name=William Lawrence)]
Michał Ziober
  • 37,175
  • 18
  • 99
  • 146
  • 1
    I tried this approach but I observe that my `@JsonAnySetter` annotated method does not get the duplicate entries when I `Sys.out`. I mean when I print the `key` and `value` within the `@JsonAnySetter` annotated method then I see only one entry for even duplicated `key` with last value. It is as if the `Jackson` has already ignored the duplicate entries before reaching out to this method. I have posted my question here if you get. a chance please let me know what should I do. https://stackoverflow.com/questions/67413028/java-jackson-jsonanysetter-does-not-store-the-values-of-duplicate-key – BATMAN_2008 May 09 '21 at 10:44
0

Create POJO class of "Friend" also, then edit your "Contacts" class like

public class Contacts {
   String id;
   List<Friend> friends;
}

And then you should get the list of Friend items

Sinan Kara
  • 26
  • 3
  • 1
    You missed the part `friends` is an array with a single object: `friends[ { {friend}, {friend} } ]`. – KunLun Jun 12 '20 at 23:16