【问题标题】:Java regex to split JSON string for every objectJava 正则表达式为每个对象拆分 JSON 字符串
【发布时间】:2021-08-17 13:00:56
【问题描述】:

我正在尝试从 JSON 文件中读取对象的数据。我想让我的函数一个一个地读取对象,而不是作为一个对象数组(这样我可以处理每个对象的异常),所以我必须制作一个正则表达式,它拆分每个 JSON 对象的数据字符串。问题是,每个对象都包含另一个对象,所以我不能在“},{”处拆分正则表达式,因为它会在内部大括号处拆分,而不是在外部大括号处。这是一个示例 JSON 文件:

{ 
  "vinForRepair" : "ABCDE123455432199",
  "dateOfRepair" : "17/08/2021",
  "mileage" : 100000,
  "items" : [ {
    "description" : "Water pump",
    "quantity" : 1,
    "price" : 120.0,
    "metric" : "UNIT
}, {         <---------This should be ignored
    "description" : "Motor oil",
    "quantity" : 1,
    "price" : 30.0,
    "metric" : "LITER"
  } ]
}, {         <---------This is where i want to split
  "vinForRepair" : "ABCDE123455432100",
  "dateOfRepair" : "15/08/2021",
  "mileage" : 250000,
  "items" : [ {
    "description" : "Break fluid",
    "quantity" : 1,
    "price" : 20.0,
    "metric" : "LITER"
  }, {       <---------This should be ignored
    "description" : "Tyre",
    "quantity" : 2,
    "price" : 80.0,
    "metric" : "UNIT"
 } ]
}

我尝试使用

String[] jsonObjectStringArray = jsonString.split("\\}, \\{\\n  \"vinForRepair\"");

在第一个对象属性处拆分,但它不起作用。

我尝试为此使用 Jackson,但它要么只读取一个对象,要么读取一组对象,正如我所说,我不想直接从文件中读取数组。

【问题讨论】:

  • “让我的函数一个一个地读取对象,而不是作为一个对象数组(这样我可以处理每个对象的异常)”为什么你不能只读取对象数组,然后为每个数组中的项,您将该项一一传递给您的函数吗?
  • @deecue 因为如果 JSON 文件中只有一个对象的属性名称无效,则不会创建整个数组

标签: java json regex string split


【解决方案1】:

您可以通过计算 json 的“深度”来代替使用正则表达式。当'{'出现时深度增加,反之,当'}'出现时深度减小。深度为零的大括号是您要拆分的地方。

private static List<String> split(String json) {
    List<String> result = new ArrayList<>();
    int depth = 0;
    int start = 0;
    for (int i = 0; i < json.length(); i++) {
        if (json.charAt(i) == '{') {
            if (depth == 0) {
                start = i;
            }
            depth++;
        }
        if (json.charAt(i) == '}') {
            depth--;
            if (depth == 0) {
                result.add(json.substring(start, i + 1));
            }
        }
    }
    return result;
}

你可以在这里运行那个方法https://ideone.com/vmnmCs

【讨论】:

  • 非常感谢!在我单独获取 JSON 对象之后,现在我可以遍历它们并检查是否每个对象都可以转换为我需要的正确类。简单有效,再次感谢!
【解决方案2】:

我并不真正赞成使用正则表达式以既定的传输格式编组/解组数据 - 这是 Jackson 擅长的。我会把正则表达式留给非常具体的捕获场景,因为它很难维护。我设法在几分钟内完成了一个解决方案,并且应该可以直接使用 Java 11+ 开箱即用(前提是您对 apache utils、jackson、hamcrest 和 logback 有习惯依赖):

编辑:我的回复可能已经太晚了,但我支持我的决定,即不实施解析算法或使用正则表达式,而是委托给杰克逊。我已经更改了我的示例,以通过自定义反序列化提供更有弹性的处理,您可以在其中决定遇到格式错误的数据时该怎么做。也许它对其他人有帮助。

public class MyJsonDeserializer {

    static final Logger logger = LoggerFactory.getLogger(MyJsonDeserializer.class);

    private static String payload = "{ \n" +
            "  \"vinForRepair\" : \"ABCDE123455432199\",\n" +
            "  \"dateOfRepair\" : \"17/08/2021\",\n" +
            "  \"mileage\" : 100000,\n" +
            "  \"items\" : [ {\n" +
            "    \"description\" : \"Water pump\",\n" +
            "    \"quantity\" : 1,\n" +
            "    \"price\" : 120.0,\n" +
            "    \"metric\" : \"UNIT\"\n" +
            "}, {" +
            "    \"description\" : \"Motor oil\",\n" +
            "    \"quantity\" : 1,\n" +
            "    \"price\" : 30.0,\n" +
            "    \"metric\" : \"LITER\"\n" +
            "  } ]\n" +
            "}, {" +
            "  \"vinForRepair\" : \"ABCDE123455432100\",\n" +
            "  \"dateOfRepair\" : \"32/x8/2021\",\n" +
            "  \"mileage\" : 250000,\n" +
            "  \"items\" : [ {\n" +
            "    \"description\" : \"Break fluid\",\n" +
            "    \"quantity\" : 1,\n" +
            "    \"price\" : 20.0,\n" +
            "    \"metric\" : \"LITER\"\n" +
            "  }, {" +
            "    \"description\" : \"Tyre\",\n" +
            "    \"quantity\" : 2,\n" +
            "    \"price\" : 80.0,\n" +
            "    \"metric\" : \"KABOOSH\"\n" +
            " } ]\n" +
            "}";


    static class OrderDto {

        public final String vinForRepair;
        public final LocalDate dateOfRepair;
        public final Long mileage;
        public final List<OrderItemDto> items;

        @JsonCreator
        public OrderDto(@JsonProperty("vinForRepair") String vinForRepair,
                        @JsonProperty("dateOfRepair") LocalDate dateOfRepair,
                        @JsonProperty("mileage") Long mileage,
                        @JsonProperty("items") List<OrderItemDto> items) {
            this.vinForRepair = vinForRepair;
            this.dateOfRepair = dateOfRepair;
            this.mileage = mileage;
            this.items = items;
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this);
        }
    }

    enum MetricEnum {
        UNIT, LITER
    }

    static class OrderItemDto {
        public final String description;
        public final Integer quantity;
        public final Double price;
        public final MetricEnum metric;

        @JsonCreator
        public OrderItemDto(@JsonProperty("description") String description,
                            @JsonProperty("quantity") Integer quantity,
                            @JsonProperty("price") Double price,
                            @JsonProperty("metric") MetricEnum metric) {
            this.description = description;
            this.quantity = quantity;
            this.price = price;
            this.metric = metric;
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this);
        }

    }

    static class OrderDtoDeserializer extends StdDeserializer<OrderDto> {

        public OrderDtoDeserializer(Class<?> vc) {
            super(vc);
        }

        public OrderDtoDeserializer() {
            this(null);
        }

        @Override
        public OrderDto deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            JsonNode jsonNode = jp.getCodec().readTree(jp);

            String vinForRepair = jsonNode.get("vinForRepair").textValue();
            LocalDate dateOfRepair = getDateOfRepair(jsonNode, ctxt);
            Long mileage = jsonNode.get("mileage").longValue();

            List<OrderItemDto> parsedItems = new ArrayList<>();

            JsonNode items = jsonNode.get("items");
            if (items.isArray()) {
                ArrayNode itemsArry = (ArrayNode)items;
                for (JsonNode item : itemsArry) {
                    String description = item.get("description").textValue();
                    Integer quantity = item.get("quantity").intValue();
                    Double price = item.get("price").doubleValue();
                    MetricEnum metric = getMetric(item, ctxt);
                    parsedItems.add(new OrderItemDto(description, quantity, price, metric));
                }
            }

            return new OrderDto(vinForRepair, dateOfRepair, mileage, List.copyOf(parsedItems));
        }

        //handle any weird values
        public LocalDate getDateOfRepair(JsonNode jsonNode, DeserializationContext ctxt) {
            String valueToConvert = jsonNode.get("dateOfRepair").textValue();
            try {
                return LocalDate.parse(valueToConvert, DateTimeFormatter.ofPattern("dd/MM/yyyy"));
            } catch (DateTimeParseException ex) {
                JsonLocation tokenLocation = ctxt.getParser().getTokenLocation();
                logger.warn("Bad input data on: [{},{}]:", tokenLocation.getLineNr(), tokenLocation.getColumnNr());
                logger.warn("{}. Will use default '{}' as date value instead.", ex.getMessage(), null);
                return null;
            }
        }

        //handle any weird values
        public MetricEnum getMetric(JsonNode item, DeserializationContext ctxt) {
            String valueToConvert = item.get("metric").textValue();
            try {
                return MetricEnum.valueOf(valueToConvert);
            } catch (IllegalArgumentException ex) {
                JsonLocation tokenLocation = ctxt.getParser().getTokenLocation();
                logger.warn("Bad input data on: [{},{}]:", tokenLocation.getLineNr(), tokenLocation.getColumnNr());
                logger.warn("Unknown '{}' value - {}. Will use default '{}' instead.", valueToConvert, ex.getMessage(), MetricEnum.UNIT);
                return MetricEnum.UNIT;
            }
        }
    }

    @Test
    public void deserialize() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();

        SimpleModule orderDtoModule = new SimpleModule();
        orderDtoModule.addDeserializer(OrderDto.class, new OrderDtoDeserializer());

        mapper.registerModules(new Jdk8Module(), orderDtoModule);

        CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(List.class, OrderDto.class);

        //read from string
        List<OrderDto> orders = mapper.readValue("[" + payload + "]", collectionType);

        Assert.assertThat(orders, Matchers.notNullValue());
        Assert.assertThat(orders.size(), Matchers.is(2));

        logger.info("{}", orders);
    }
}

【讨论】:

  • 如果文件中的所有数据都正确输入,则此方法有效,但如果文件中存在单个错误,例如 - 错误的数据字段名称,则不会创建整个列表跨度>
  • 我用一个改进的例子编辑了我的答案。
猜你喜欢
  • 1970-01-01
  • 2011-10-16
  • 1970-01-01
  • 2021-06-17
  • 2011-07-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多