【问题标题】:JSON validation returns different resultsJSON 验证返回不同的结果
【发布时间】:2021-07-20 20:36:56
【问题描述】:

我创建了一个最小的应用程序来调试以下问题:

应用程序正在创建一个包含 GeoJsonPoint 的 Java 对象。当对象根据自动创建的 json 模式进行验证时,我在多次启动应用程序时会收到不同的结果。例如,我启动应用程序 5 次,结果是“Json 无效!”。当我再次启动应用程序时,我收到“Json 成功验证”的结果。

json 无效时的错误消息告诉我: /geoPosition/coordinates: 实例类型(数组)不匹配任何允许的原始类型(允许:[object])

以下行随机返回不同的 json 架构:

JsonNode fstabSchema = schemaFactory.createSchema(inputObj.getClass());

我不明白这是随机发生的。以前有人见过这种行为吗?

为了在运行时排除依赖问题,我创建了一个包含所有依赖项的 jar(jar-with-dependencies)

以下是我的文件:

pom.xml 依赖:

<dependencies>

    <dependency>
        <groupId>com.github.reinert</groupId>
        <artifactId>jjschema</artifactId>
        <version>1.16</version>
    </dependency>
    
    <dependency>
        <groupId>com.github.java-json-tools</groupId>
        <artifactId>json-schema-validator</artifactId>
        <version>2.2.12</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
        <version>1.18.16</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-mongodb</artifactId>
        <version>3.2.2</version>
    </dependency>

</dependencies>

App.java:

package de.s2.json.test;

import java.util.ArrayList;

import com.github.fge.jsonschema.core.exceptions.ProcessingException;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;

public class App 
{
    public static void main( String[] args )
    {
        Address address = new Address();

        address.setCountry("Deutschland");

        GeoJsonPoint geoPoint = new GeoJsonPoint(12, 23);
        address.setGeoPosition(geoPoint);

        ArrayList<String> ret = null;

        try {
            ret = Toolbox.validateJson(address);
        } catch (ProcessingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        if(ret != null) {
            System.out.println("Json not valid!");
            for (int i = 0; i < ret.size(); i++) {
                System.out.println(ret.get(i));
            }
        } else {
            System.out.println("Json successfully validated");
        }
    }
}

工具箱.java:

package de.s2.json.test;

import java.util.ArrayList;
import java.util.Iterator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.reinert.jjschema.v1.JsonSchemaV4Factory;
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
import com.github.fge.jsonschema.core.report.ProcessingMessage;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.main.JsonSchema;
import com.github.fge.jsonschema.main.JsonSchemaFactory;


public class Toolbox {

    public static <T> ArrayList<String> validateJson(T inputObj) throws ProcessingException {

        com.github.reinert.jjschema.v1.JsonSchemaFactory schemaFactory = new JsonSchemaV4Factory();
        schemaFactory.setAutoPutDollarSchema(true);
        JsonNode fstabSchema = schemaFactory.createSchema(inputObj.getClass()); // <= here I get different results

        final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
        final JsonSchema schema = factory.getJsonSchema(fstabSchema);

        ObjectMapper objectMapper = new ObjectMapper();

        JsonNode baseReceiptJson = objectMapper.convertValue(inputObj, JsonNode.class);
        ProcessingReport report;

        report = schema.validate(baseReceiptJson);

        ArrayList<String> validationErrorDetails = new ArrayList<String>();

        if (!report.isSuccess()) {

            StringBuilder builder = new StringBuilder();

            builder.append("Not all required fields are filled with data");
            builder.append(System.getProperty("line.separator"));

            for (Iterator<ProcessingMessage> i = report.iterator(); i.hasNext();) {
                
                ProcessingMessage msg = i.next();
                builder.append(msg.asJson().findValue("instance").findValue("pointer").toString());
                builder.append(": ");
                builder.append(msg.getMessage());

                String detail = msg.asJson().findValue("instance").findValue("pointer").toString() + ": " + msg.getMessage();

                detail = detail.replace("\"", "");
                validationErrorDetails.add(detail);

                builder.append("\n");
            }
        
            return validationErrorDetails;
        }
        return null;
    } 
}

地址.java:

package de.s2.json.test;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.github.reinert.jjschema.Attributes;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Address
{

    @JsonProperty("country")
    private String country = "";

    @JsonProperty("geoPosition")
    @Attributes(required=false, description="longitude and latitude (it is initialized with 0,0)")
    private GeoJsonPoint geoPosition = new GeoJsonPoint(0, 0);
    
}

非常感谢您的支持!


更新 1: 回答 Hiran Chaudhuri


如果验证失败,则架构如下所示:

{
  "type": "object",
  "properties": {
    "country": {
      "type": "string"
    },
    "geoPosition": {
      "type": "object",
      "properties": {
        "coordinates": {
          "type": "object"
        },
        "TYPE": {
          "type": "string"
        },
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        }
      },
      "description": "longitude and latitude (it is initialized with 0,0 which is inside the ocean)"
    }
  },
  "$schema": "http://json-schema.org/draft-04/schema#"
}

如果成功,则如下所示:

{
  "type": "object",
  "properties": {
    "country": {
      "type": "string"
    },
    "geoPosition": {
      "type": "object",
      "properties": {
        "coordinates": {
          "type": "array",
          "items": {
            "type": "number"
          }
        },
        "TYPE": {
          "type": "string"
        },
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        }
      },
      "description": "longitude and latitude (it is initialized with 0,0 which is inside the ocean)"
    }
  },
  "$schema": "http://json-schema.org/draft-04/schema#"
}

正如已经在错误消息中指出的那样,一次坐标是“对象”,另一次是“数组”


更新 2


由于这个问题阻止了我继续我的项目,我添加了一个“丑陋的黑客”来检查架构是否包含 GeoJsonPoint。如果包含,它将使用正确的值对其进行修补。

package de.s2.json.test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.reinert.jjschema.v1.JsonSchemaV4Factory;
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
import com.github.fge.jsonschema.core.report.ProcessingMessage;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.main.JsonSchema;
import com.github.fge.jsonschema.main.JsonSchemaFactory;


public class Toolbox {

    public static <T> ArrayList<String> validateJson(T inputObj) throws ProcessingException {

        com.github.reinert.jjschema.v1.JsonSchemaFactory schemaFactory = new JsonSchemaV4Factory();
        schemaFactory.setAutoPutDollarSchema(true);
        JsonNode fstabSchema = schemaFactory.createSchema(inputObj.getClass());

        ArrayList<String> validationErrorDetails = new ArrayList<String>();

        // *****************************************************************************************
        // /!\ this is an ugly hack /!\
        // Randomly the function createSchema() returns an 'array' or 'object' type for coordinates
        // of the geoJsonPoint. The correct value should be array. The following code checks if a 
        // geoJsonPoint is inside the schema and overwrites it with the correct value.
        // TODO: fix this issue correctly
        // *****************************************************************************************

        try {
            String jsonCoordinate = "{\"type\": \"array\",\"items\": {\"type\": \"number\"}}";
            ObjectMapper mapper = new ObjectMapper();
            JsonNode jsonNodeCoordinate = mapper.readTree(jsonCoordinate);
            JsonNode coordJsonNode = fstabSchema.findValue("geoPosition").get("properties");
            ObjectNode coordObjNode = (ObjectNode) coordJsonNode;
            coordObjNode.set("coordinates", jsonNodeCoordinate);
        } catch (JsonProcessingException e) {
            validationErrorDetails.add("Could not patch geoPosition");
            return validationErrorDetails;
        } catch (IOException e) {
            validationErrorDetails.add("Could not patch geoPosition");
            return validationErrorDetails;
        } catch (NullPointerException e) {
            // this means that geoPosition could not be found
            // we do nothing ...
        }
        // ******************

        final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
        final JsonSchema schema = factory.getJsonSchema(fstabSchema);

        ObjectMapper objectMapper = new ObjectMapper();

        JsonNode baseReceiptJson = objectMapper.convertValue(inputObj, JsonNode.class);
        ProcessingReport report;

        report = schema.validate(baseReceiptJson);

        if (!report.isSuccess()) {

            StringBuilder builder = new StringBuilder();

            builder.append("Not all required fields are filled with data");
            builder.append(System.getProperty("line.separator"));

            for (Iterator<ProcessingMessage> i = report.iterator(); i.hasNext();) {
                
                ProcessingMessage msg = i.next();
                builder.append(msg.asJson().findValue("instance").findValue("pointer").toString());
                builder.append(": ");
                builder.append(msg.getMessage());

                String detail = msg.asJson().findValue("instance").findValue("pointer").toString() + ": " + msg.getMessage();

                detail = detail.replace("\"", "");
                validationErrorDetails.add(detail);

                builder.append("\n");
            }
        
            return validationErrorDetails;
        }
        return null;
    } 
}

【问题讨论】:

    标签: java json spring


    【解决方案1】:

    通过对您生成的 JSON Schema 进行序列化,我们现在可以比较它们是否相同或实际差异有多大。考虑到生成模式的方法需要一些输入参数。

    我从来都不喜欢根据实际数据生成架构。 为什么你不严格定义你想要的架构,让你的代码衡量你是否真的满足那个结构?通过这种方式,您可以将架构用作应用程序组件之间的合同,因为它是记录在案的而不是动态生成的。

    这仍然不能回答为什么会随机发生的问题。但对于您和其他人来说,这可能是一个更好的做法。

    【讨论】:

    • 虽然这是了解正在发生的事情的一个很好的提示,但我认为这并不是一个真正的答案,应该是对这个问题的评论。
    • 感谢您的评论,我添加了更多信息。我仍然不明白为什么会随机发生。
    • 不幸的是,对于这个项目来说,为每个对象静态定义其自己的架构会耗费太多精力。
    • 你是对的。我会使用一类对象的模式。如果这对您来说还不够,为什么还要为架构烦恼呢?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-18
    • 2012-11-30
    • 2019-06-03
    • 2013-12-07
    • 1970-01-01
    相关资源
    最近更新 更多