在JSON上应用掩码以仅保留强制数据



我有一个API,它接受一些潜在的强制数据来创建临时会话:

例如:首先,我的POST /signup端点用户需要向我发送以下数据:

{
"customer": {
"age": 21,
"ssn": "000 00 0000",
"address": {
"street": "Customer St.",
"phone": "+66 444 333 222"
}
}
}

让我们称之为JSON a

另一方面,我有一些法律合作伙伴需要这些数据中的一些,但不是全部:

例如

{
"customer": {
"ssn": "The SSN is mandatory to register against XXX Company",
"address": {
"phone": "XXX Company will send a text message to validate your registration"
}
}
}

让我们称之为JSON b

由于最近的法律限制,在我的信息系统中,我只能保留强制性数据,供用户携带他选择的工作流程

因此我的问题:是否有一个函数(内置于Jackson或其他JSON处理库中,或您推荐的算法(可以应用于给定的JSON bJSON a,它将输出以下JSON:

{
"customer": {
"ssn": "000 00 0000",
"address": {
"phone": "+66 444 333 222"
}
}
}

思考一下,我发现了一个可能是解决方案的东西:

  • JSON aJSON b合并,冲突时取JSON b值,将结果命名为JSON c
  • JSON cJSON a之间进行diff(drop等于data(,冲突时取JSON a

我知道使用Jackson合并两个JSON可以使用com.fasterxml.jackson.databind.ObjectMapper#readerForUpdating完成,所以我的问题可以归结为:有没有办法在两个JSON之间进行区分并给出冲突解决函数?

我建议使用基于令牌/事件/流的解决方案。以下只是使用微小解析器/生成器库的示例https://github.com/anatolygudkov/green-jelly(Gson和Jackson还提供面向流媒体的API(:

import org.green.jelly.AppendableWriter;
import org.green.jelly.JsonEventPump;
import org.green.jelly.JsonNumber;
import org.green.jelly.JsonParser;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class FilterMyJson {
private static final String jsonToFilter = "{n" +
"  "customer": {n" +
"    "age": 21,n" +
"    "ssn": "000 00 0000",n" +
"    "address": {n" +
"      "street": "Customer St.",n" +
"      "phone": "+66 444 333 222"n" +
"    }n" +
"  }n" +
"}";
public static void main(String[] args) {
final StringWriter result = new StringWriter();
final JsonParser parser = new JsonParser();
parser.setListener(new MyJsonFilter(result, "age", "street"));
parser.parse(jsonToFilter); // if you read a file with a buffer,
// call parse() several times part by part in a loop until EOF
parser.eoj(); // and then call .eoj()
System.out.println(result);
}
static class MyJsonFilter extends JsonEventPump {
private final Set<String> objectMembersToFilter;
private boolean currentObjectMemberIsAllowed;
MyJsonFilter(final Writer output, final String... objectMembersToFilter) {
super(new AppendableWriter<>(output));
this.objectMembersToFilter = new HashSet<>(Arrays.asList(objectMembersToFilter));
}
@Override
public boolean onObjectMember(final CharSequence name) {
currentObjectMemberIsAllowed =
!objectMembersToFilter.contains(name.toString());
return super.onObjectMember(name);
}
@Override
public boolean onStringValue(final CharSequence data) {
if (!currentObjectMemberIsAllowed) {
return true;
}
return super.onStringValue(data);
}
@Override
public boolean onNumberValue(final JsonNumber number) {
if (!currentObjectMemberIsAllowed) {
return true;
}
return super.onNumberValue(number);
}
}
}

打印:

{
"customer":
{
"ssn": "000 00 0000",
"address":
{
"phone": "+66 444 333 222"
}
}
}

代码相当简化。目前,它只过滤掉字符串和数字标量。不支持对象层次结构。因此,在某些情况下,您可能需要改进代码。

此类解决方案的道具:

  • 文件/数据不需要完全加载到内存中可以毫无问题地处理兆欧/吉
  • 它的工作速度要快得多,尤其是对于大文件
  • 使用此模式可以很容易地实现任何自定义类型/规则的转换。例如,很容易对过滤器进行参数化,不必每次更改数据结构时都重新编译代码

感谢https://github.com/algesten/jsondiff这让我找到了一个更容易维护的关键字https://github.com/java-json-tools/json-patch(能够使用自己的ObjectMapper(,我找到了我的答案(见mask()方法(:

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import com.fasterxml.jackson.annotation.JsonMerge;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.fge.jsonpatch.diff.JsonDiff;
import java.math.BigInteger;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.ToString;
import org.junit.jupiter.api.Test;
class JsonPatchTest {
private static final ObjectMapper mapper = new ObjectMapper();
@Getter
@Builder
@ToString
@EqualsAndHashCode
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class Data {
@JsonMerge Customer customer;
@Getter
@Builder
@ToString
@EqualsAndHashCode
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class Customer {
@JsonMerge BigInteger age;
@JsonMerge String     ssn;
@JsonMerge Address    address;
@Getter
@Builder
@ToString
@EqualsAndHashCode
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class Address {
@JsonMerge String street;
@JsonMerge String phone;
}
}
@SneakyThrows
Data merge(Data parent) {
var originCopyAsString = mapper.writerFor(this.getClass()).writeValueAsString(this);
var parentAsString = mapper.writerFor(this.getClass()).writeValueAsString(parent);
var parentCopy     = mapper.readerFor(this.getClass()).readValue(parentAsString);
var clone = mapper.readerForUpdating(parentCopy).readValue(originCopyAsString);
return (Data) clone;
}
}
@SneakyThrows
@Test
void mask() {
final var diff = JsonDiff.asJsonPatch(mapper.readTree(jsonC()), mapper.readTree(jsonB()));
final var masked = diff.apply(mapper.readTree(jsonA())).toPrettyString();
assertThat(masked).isEqualToIgnoringWhitespace(masked());
}
private String jsonA() {
return "{n"
+ "  "customer": {n"
+ "    "age": 21,n"
+ "    "ssn": "000 00 0000",n"
+ "    "address": {n"
+ "      "street": "Customer St.",n"
+ "      "phone": "+66 444 333 222"n"
+ "    }n"
+ "  }n"
+ "}";
}
private String jsonB() {
return "{n"
+ "  "customer": {n"
+ "    "ssn": "The SSN is mandatory to register against XXX Company",n"
+ "    "address": {n"
+ "      "phone": "XXX Company will send a text message to validate your registration"n"
+ "    }n"
+ "  }n"
+ "}";
}
@SneakyThrows
private String jsonC() {
final Data dataA = mapper.readerFor(Data.class).readValue(jsonA());
final Data dataB = mapper.readerFor(Data.class).readValue(jsonB());
final Data merged = dataB.merge(dataA);
return mapper.writerFor(Data.class).writeValueAsString(merged);
}
@SneakyThrows
private String masked() {
return "{n"
+ "  "customer": {n"
+ "    "ssn": "000 00 0000",n"
+ "    "address": {n"
+ "      "phone": "+66 444 333 222"n"
+ "    }n"
+ "  }n"
+ "}";
}
}

最新更新