4

I have a number of json objects concatenated into one string, and need to parse all of them. Simple example:

String jsonStr = "{"name":"peter","age":40}{"name":"laura","age":50}"

When using an ObjectMapper of jackson to parse this, it finds and reads the first json correctly, and drops the rest of the string.

ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(jsonStr);
System.out.println(rootNode);

Gives output {"name":"peter","age":20}

Is there any way (in Jackson or another framework) of e.g. returning the number of read characters, or the rest of the string, or an array of JsonNodes?

I found questions with the same goal in JavaScript and in Python, where it was recommended to split by }{ or regex to reformat this to a json array, but I still hope for a more elegant solution.

Bastian35022
  • 1,092
  • 1
  • 10
  • 18
  • 1
    just append a `]` and prepend a `[` and then read it as an array – Lino Aug 13 '18 at 11:06
  • `[{"name":"peter","age":40},{"name":"laura","age":50}]` use this way. – Sumesh TG Aug 13 '18 at 11:07
  • @Lino still, you need to append add commas between json objects – Hemant Patel Aug 13 '18 at 11:10
  • I don't have control over the input, have to parse it as is. The square brackets are easy, but inserting the comma is not trivial to do in a fail-safe way, and brings some other problems, which is why I'd like to avoid this, if possible. – Bastian35022 Aug 13 '18 at 11:11

4 Answers4

6

You don't need to modify your input as suggested by others, just use below code.

Main Method

public static void main(String[] args) throws IOException {

    ObjectMapper mapper = new ObjectMapper();
    JsonFactory factory = new JsonFactory(mapper);

    JsonParser parser = factory.createParser(new File("config.json"));
    // factory.createParser(String) and many other overload methods
    // available, byte[], char[], InputStream etc.

    Iterator<Person> persons = parser.readValuesAs(Person.class);
    while(persons.hasNext()) {
        Person p = persons.next();
        System.out.printf("%s: %d%n", p.getName(), p.getAge());
    }
}

Person Class

public class Person {

    private String name;
    private int age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}  

config.json file

{"name":"peter","age":40}{"name":"laura","age":50}

Program Output

peter: 40
laura: 50

Library used

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.5</version>
</dependency>
Hemant Patel
  • 3,160
  • 1
  • 20
  • 29
  • Excellent, this put me onto the right track, thanks a lot. Do you by chance know how to handle incomplete data (i.e. having e.g. `{"name":"peter","age":40}{"name":"lau` as input)? Found [this thread](https://stackoverflow.com/questions/38416158/how-to-properly-parse-streaming-json-with-jackson?rq=1), which gives a not-so-nice solution, unfortunately. – Bastian35022 Aug 13 '18 at 12:15
  • @Bastian35022 why is it your job to fix invalid data? if someone is executing your code with invalid data, then it should just exit and throw an exception. – Lino Aug 13 '18 at 12:24
  • @Lino The incomplete data can occur because of data delivery over TCP sockets, and is something quite common, which cannot be avoided without any protocol on top of TCP to restructure the data into full app layer packets. In case the data is incomplete, I just need to wait for more data to arrive, or discard it in case the connection broke down. – Bastian35022 Aug 13 '18 at 12:30
  • Then there is no need to try to parse that input, as you will either wait or throw it away – Lino Aug 13 '18 at 12:31
  • It's a real-time application, with stronger latency requirements than common web applications. Complete json objects should be parsed and processed asap, but the case of getting partial json objects can occur and needs to be addressed. – Bastian35022 Aug 13 '18 at 12:35
  • In case of invalid input, first json will be parsed successfully and second will throw error. – Hemant Patel Aug 13 '18 at 13:23
  • In case of exception (JsonEOFException) I tried fetching the source json, so that you can prepend it in the next payload, but didn't find any solution. You get the underlying JsonParser from the JsonEOFException, You can play with Jsonparser to get some info. – Hemant Patel Aug 13 '18 at 13:30
1

In Json, an object structure starts with { and ends with }. Hence ObjectMapper thinks that there is nothing more to process as soon as it encounters }.

In Json an array is indicated with []. So if you wish to have multiple elements / objects it needs to be enclosed with [] and a comma separating individual objects

"[
  {"name":"peter","age":40},
  {"name":"laura","age":50}
]"
pvpkiran
  • 25,582
  • 8
  • 87
  • 134
  • 1
    Inserting the comma here is the issue. Of course it's possible, but makes the code more ugly and adds unnecessary complexity, which is why a solution as described would be appreciated. – Bastian35022 Aug 13 '18 at 11:23
0

You can wrap the input with brackets [] and then replace every }{ with },{, and then finally parse the string as array:

String input = "{\"name\":\"peter\",\"age\":40}{\"name\":\"laura\",\"age\":50}"
String jsonArray = "[" + input.replace("}{", "},{") + "]";

ObjectMapper mapper = new ObjectMapper();
JsonNode parsedJsonArray = mapper.readTree(jsonArray);
Lino
  • 19,604
  • 6
  • 47
  • 65
  • 1
    Of course it's possible, but makes the code more ugly and adds unnecessary complexity, which is why a solution as described would be appreciated. – Bastian35022 Aug 13 '18 at 11:23
0

you have to modify your JSON

String jsonStr = "[{\"name\":\"peter\",\"age\":40},{\"name\":\"laura\",\"age\":50}]";

There are multiple ways to convert JSON into java object.

1st Way

public JsonNode convertIntoJsonNode(String jsonStr)
    {
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode rootNode = null;
        try {
            rootNode = objectMapper.readTree(jsonStr);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return rootNode;
    }

2nd Ways // check also imports

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
// User Model
    class User{
        private String name;
        private Integer age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
        @Override
        public String toString() {
            return "User [name=" + name + ", age=" + age + "]";
        }
    }
//Coversion Method
public List<User> convertIntoObject(String jsonStr)
    {
        ObjectMapper objectMapper = new ObjectMapper();
        User[] myObjects = null;
        try {
            myObjects = objectMapper.readValue(jsonStr, User[].class);


        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return Arrays.asList(myObjects);
    }

3rd way you can directly parse without array

public List<User> convertIntothirdway(String jsonStr)
    {
        ObjectMapper objectMapper = new ObjectMapper();
        List<User> userList = null;
        try {
             userList = objectMapper.readValue(jsonStr, new TypeReference<List<User>>(){});
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return userList;
    }

4th way

List<User> userList = mapper.readValue(jsonInput, mapper.getTypeFactory().constructCollectionType(List.class, User.class));
Faiz Akram
  • 559
  • 4
  • 10
  • where is the 2. way? – Lino Aug 13 '18 at 11:34
  • added the second way – Faiz Akram Aug 13 '18 at 11:36
  • 1
    I can't change the input, unfortunately. And as described, I'd prefer not applying string replace- or regex-based modifications. – Bastian35022 Aug 13 '18 at 11:40
  • please check your JSON that is not valid JSON. check the josn on given link https://jsonformatter.curiousconcept.com/ – Faiz Akram Aug 13 '18 at 12:04
  • @FaizAkram Request you to read the question carefully, it is mentioned that multiple valid json objects are concatenated, he needs to read first valid json and then second and then so-on. Also he mentioned that he is aware of some solution which requires replacing `}{` but he didn't prefer them. – Hemant Patel Aug 13 '18 at 12:10
  • The concatenation of multiple JSON objects in one stream comes from TCP socket delivery, and is something quite common, which cannot be avoid without any protocol on top of TCP to restructure the data into full app layer packets. – Bastian35022 Aug 13 '18 at 12:14