背景

在异构系统中,不同的平台数据通信的方式多种多样,当前web系统流行restful接口通信方法。

restful接口通信的灵魂无非是json。本文适用于c#中使用newtonsoft 生成的json向java开放的restful接口通信。

以及SAP向java的restful接口进行通信。

比较接收到的json如下

{
"json": "{\"HEADER\":{\"AUFNR\":\"100002312156\",\"PLNBEZ\":\"20481438\",\"GAMNG\":\"100.000\",\"GMEIN\":\"PC\",\"GSTRP\":\"20211227\",\"GLTRP\":\"20211229\",\"WERKS\":\"1001\",\"ZKUNNR\":\"\",\"ZNAME1\":\"\",\"STATUS\":\"1\",\"ZLS\":\"l2211240013\",\"FEVOR\":\"101\"},\"OPERATES\":[{\"AUFNR\":\"100002312156\",\"VORNR\":\"0010\",\"ARBPL\":\"ctkx02\",\"LTXA1\":\"开线\",\"SORTL\":\"0010\",\"BMSCH\":\"1.000\",\"VGW01\":\"1.460\",\"VGE01\":\"S\",\"VGW02\":\"\",\"VGE02\":\"\",\"VGW03\":\"\",\"VGE03\":\"\",\"VGW04\":\"\",\"VGE04\":\"\",\"VGW05\":\"\"}],\"COMPONENTS\":[{\"AUFNR\":\"100002312156\",\"MATNR\":\"a01011551\",\"BDMNG\":\"784.000\",\"ENMNG\":\"520.000\",\"MEINS\":\"M\",\"LGORT\":\"4001\",\"VORNR\":\"0010\",\"RGEKZ\":\"\",\"WERKS\":\"1001\",\"POSNR\":\"0001\",\"RSNUM\":\"0012552966\",\"LOEKZ\":\"\",\"DUMPS\":\"\",\"MENGE\":\"0.280\",\"ZMEINS\":\"M\"},{\"AUFNR\":\"100002312156\",\"MATNR\":\"a01011551\",\"BDMNG\":\"2800.000\",\"ENMNG\":\"1000.000\",\"MEINS\":\"PC\",\"LGORT\":\"4001\",\"VORNR\":\"0010\",\"RGEKZ\":\"\",\"WERKS\":\"1001\",\"POSNR\":\"0002\",\"RSNUM\":\"0012552966\",\"LOEKZ\":\"\",\"DUMPS\":\"\",\"MENGE\":\"1.280\",\"ZMEINS\":\"M\"}]}"
}

服务端的实体如下

package com.www.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.www.common.component.json.SAPOrderReceiveDTODeserializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.builder.ToStringBuilder;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Set;


@Data
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModel("SAP订单接收接口入参")
public class SAPOrderReceiveDTO {

/***
* 订单头
*/
@Valid
@ApiModelProperty(value = "订单号 参见SAPMoHeaderDTO类型")
@NotNull(message = "订单头必填")
@JsonProperty(value = "HEADER")
private SAPMoHeaderDTO header;

/***
* 订单工艺
*/
@Valid
@ApiModelProperty(value = "订单工序 参见SAPMoOperateDTO类型")
@NotEmpty(message = "订单工序必填")
@JsonProperty(value = "OPERATES")
private Set<SAPMoOperateDTO> operateList;
/***
* 订单组件
*/
@Valid
@ApiModelProperty(value = "订单组件 参见SAPMoComponentDTO类型")
@NotEmpty(message = "订单组件必填")
@JsonProperty(value = "COMPONENTS")
private Set<SAPMoComponentDTO> componentList;
}

 

解决方法

笔者日常使用springboot进行web项目开发,json解析框架是springboot默认的Jackson,本文以Jackson解析带控制字符的json为例。

理论上fastjson是支持这种非标json的,不过不在本文讨论范围内,笔者不太喜欢fastjson,恕不回答fastjson相关的解决方案。

实体改造

package com.www.dto;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.www.common.component.json.SAPOrderReceiveDTODeserializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.builder.ToStringBuilder;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Set;


@Data
@EqualsAndHashCode(callSuper = false)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModel("SAP订单接收接口入参")
public class SAPOrderReceiveDTO {

@Valid
@JsonDeserialize(using = SAPOrderReceiveDTODeserializer.class)
@JsonProperty(value = "json")
@NotNull(message = "无效的json数据")
private JsonNode json;

@Data
public static final class JsonNode
{
/***
* 订单头
*/
@Valid
@ApiModelProperty(value = "订单号 参见SAPMoHeaderDTO类型")
@NotNull(message = "订单头必填")
@JsonProperty(value = "HEADER")
private SAPMoHeaderDTO header;

/***
* 订单工艺
*/
@Valid
@ApiModelProperty(value = "订单工序 参见SAPMoOperateDTO类型")
@NotEmpty(message = "订单工序必填")
@JsonProperty(value = "OPERATES")
private Set<SAPMoOperateDTO> operateList;
/***
* 订单组件
*/
@Valid
@ApiModelProperty(value = "订单组件 参见SAPMoComponentDTO类型")
@NotEmpty(message = "订单组件必填")
@JsonProperty(value = "COMPONENTS")
private Set<SAPMoComponentDTO> componentList;
}

}

新增自定义反序列化类

package com.www.common.component.json;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.www.dto.SAPOrderReceiveDTO;
import org.springframework.util.StringUtils;

import java.io.IOException;

public class SAPOrderReceiveDTODeserializer extends JsonDeserializer<SAPOrderReceiveDTO.JsonNode> {
JacksonMapperEnhance objectMapper = new JacksonMapperEnhance();

@Override
public SAPOrderReceiveDTO.JsonNode deserialize(final JsonParser jp, final DeserializationContext ctx) throws IOException, JsonProcessingException {
final TreeNode node = jp.getCodec().readTree(jp);

String json = null;
if ((node instanceof TextNode)) {
json = ((TextNode) node).textValue();
}
if(node instanceof ObjectNode)
{
json = (((ObjectNode)node).get("json")).textValue();
}

if(StringUtils.isEmpty(json))
return null;


objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);

//反序列化:空字符串的对象直接转换成null对象
objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT,true);

// 忽略字段大小写
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);

final SAPOrderReceiveDTO.JsonNode jsonNode = objectMapper.readValue(toHalfWidth(json), SAPOrderReceiveDTO.JsonNode.class);

return jsonNode;
}

/**
* 转半角的函数(DBC case)<br/><br/>
* 全角空格为12288,半角空格为32
* 其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248
*
* @param input 任意字符串
* @return 半角字符串
*/
public String toHalfWidth(String input) {
char[] c = input.toCharArray();
for (int i = 0; i < c.length; i++) {
if (c[i] == 12288) {
//全角空格为12288,半角空格为32
c[i] = (char) 32;
continue;
}
if (c[i] > 65280 && c[i] < 65375)
//其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248
c[i] = (char) (c[i] - 65248);
}
return new String(c);
}
}