Джексон прекращает чтение входной строки после первого закрытия фигурной скобки

#java #json #jackson

#java #json #джексон

Вопрос:

У меня есть некоторый код, который принимает массив байтов. Эти байты, преобразованные в строку, должны быть допустимой строкой JSON. Если это не так, он преобразует строку в допустимый JSON, используя «Uknown» в качестве ключа.

Это работает нормально, за исключением одного крайнего случая, который я нашел. Если я передаю ему строку, в которой содержится более одной допустимой строки JSON, он анализирует только первую строку и считает ее допустимой JSON. Я бы предпочел, чтобы он оценил всю строку и определил, что это недопустимый JSON, поскольку это 2 или более отдельных допустимых строк JSON. Затем это превратило бы отдельные строки JSON в одну допустимую строку JSON, как это происходит для любой другой строки, которая не является допустимой JSON.

Я использую Jackson 2.8.1.

Ниже приведено небольшое приложение, которое демонстрирует проблему. Любая помощь будет оценена.

 import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class EnsureValidJSON {

  private static ObjectMapper objectMapper = new ObjectMapper();

  public static void main(String[] args) {

    String input = "{"Message1" : "This is the first message"}{"Message2" : "This is the second message."}";
    System.out.println("input: "   input);

    byte[] msg = input.getBytes();
    try {
      msg = ensureMsgIsValidJson(msg);
    } catch (IOException e) {
      // Default to Unknown:Unknown
      msg = "{"Unknown" : "Unknown"}".getBytes();
    }

    System.out.println("output: "   new String(msg));
  }

  private static boolean isJSONValid(byte[] msg) {
    boolean isValid = false;
    try {
      JsonNode jsonNode = objectMapper.readTree(msg);

      // Print out the field names and their values to show that it is only parsing the first Json String.
      Iterator<String> itr = jsonNode.fieldNames();
      while (itr.hasNext()) {
        String fieldName = itr.next();
        System.out.print(fieldName   ": ");
        System.out.println(jsonNode.get(fieldName));
      }
      isValid = true;
    } catch (IOException e) {
      String err = String.format("%s is an invalid JSON message. We will attempt to make the message valid JSON. Its key will be 'Unknown'.", new String(msg));
      System.out.println(err);
    }

    return isValid;
  }

  private static byte[] ensureMsgIsValidJson(byte[] msg) throws IOException {
    if (isJSONValid(msg)) {
      return msg;
    }
    return createValidJSON(msg);

  }

  private static byte[] createValidJSON(byte[] msg) throws IOException {
    JsonFactory factory = new JsonFactory();
    try (OutputStream out = new ByteArrayOutputStream()) {
      JsonGenerator generator = factory.createGenerator(out);
      generator.writeBinary(msg);

      JsonNodeFactory nodeFactory = new JsonNodeFactory(false);
      ObjectNode validated = nodeFactory.objectNode();
      objectMapper.writeTree(generator, validated);
      validated.put("Unknown", new String(msg));
      byte[] validatedBytes = objectMapper.writeValueAsBytes(validated);
      String message = String.format("Message(%s) was successfully converted to a valid JSON message: %s", new String(msg), new String(validatedBytes));
      System.out.println(message);
      return validatedBytes;
    }
  }

}
  

Ответ №1:

Мне пришлось использовать объект jackson JsonParser для подсчета количества открывающих и закрывающих фигурных скобок. Если количество равно 0 и в строке ничего не осталось, в ней есть только одна строка JSON. Мне также пришлось добавить код, чтобы проверить, является ли значение числовым, потому что метод readTree ObjectMapper не выдаст исключение IOException, если значение принимает значение number.

Это больше кода, который я хотел написать, чтобы это сделать, но это работает:

 import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Scanner;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class EnsureValidJSON {

  private static ObjectMapper objectMapper = new ObjectMapper();

  public static void main(String[] args) {
    if (args.length == 0) {
      System.err.println("You must pass at least one String to be validated.");
    } else {

      for (String arg : args) {
        System.out.println("input: "   arg);
        byte[] msg = arg.getBytes();
        try {
          msg = ensureMsgIsValidJson(msg);
        } catch (IOException e) {
          msg = "{"Unknown" : "Unknown"}".getBytes();
        }
        System.out.println("output: "   new String(msg));
      }
    }
  }

  private static boolean isJSONValid(byte[] msg) {
    return isJSONFormat(msg) amp;amp; isJSONOneMessage(msg);
  }

  private static boolean isJSONFormat(byte[] msg) {
    boolean isValid = false;
    String rawString = new String(msg).trim();
    try (Scanner sc = new Scanner(rawString)) {
      objectMapper.readTree(msg);
      // If the value evaluates to a number, objectMapper.readTree will not throw an Exception, so check that here.
      if (sc.hasNextLong() || sc.hasNextDouble()) {
        String err = String.format("%s is an invalid JSON message because it is numeric.", rawString);
        System.out.println(err);
      } else {
        isValid = true;
      }
    } catch (IOException e) {
      String err = String.format("%s is an invalid JSON message. We will attempt to make the message valid JSON. Its key will be 'Unknown'.", rawString);
      System.out.println(err);
    }

    return isValid;
  }

  private static boolean isJSONOneMessage(byte[] msg) {
    boolean isValid = false;
    try {
      JsonParser parser = objectMapper.getFactory().createParser(msg);
      JsonToken token;
      // balance will increment with each opening curly bracket and decrement with each closing curly bracket. We'll use this to ensure that this is only one JSON message.
      int balance = 0;
      while ((token = parser.nextToken()) != null) {
        if (token.isStructStart()) {
          balance  ;
        } else if (token.isStructEnd()) {
          balance--;
        }
        if (balance == 0) {
          break;
        }
      }
      isValid = parser.nextToken() == null;
    } catch (IOException e) {
      String err = String.format("'%s' is an invalid JSON message due to the following error: '%s'. We will attempt to make the message valid JSON. Its key will be 'Unknown'.", new String(msg),
          e.getMessage());
      System.out.println(err);
    }

    return isValid;
  }

  private static byte[] ensureMsgIsValidJson(byte[] msg) throws IOException {
    return isJSONValid(msg) ? msg : createValidJSON(msg);
  }

  private static byte[] createValidJSON(byte[] msg) throws IOException {
    JsonFactory factory = new JsonFactory();
    try (OutputStream out = new ByteArrayOutputStream()) {
      JsonGenerator generator = factory.createGenerator(out);
      generator.writeBinary(msg);

      JsonNodeFactory nodeFactory = new JsonNodeFactory(false);
      ObjectNode validated = nodeFactory.objectNode();
      objectMapper.writeTree(generator, validated);
      validated.put("Unknown", new String(msg));
      byte[] validatedBytes = objectMapper.writeValueAsBytes(validated);
      String message = String.format("Message(%s) was successfully converted to a valid JSON message: %s", new String(msg), new String(validatedBytes));
      System.out.println(message);
      return validatedBytes;
    }
  }

}