일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- spring webflux
- JSON 분리
- multi update
- 날짜형을 문자형으로
- JSON 분할
- 스테이지에 올리기
- 성능개선
- 마이바티스 트랜잭션
- 스프링 배치 공식문서
- ChainedTransactionManager #분산데이터베이스 #Spring Boot #MyBatis
- 무시하기
- JobExecutionAlreadyRunningException
- JSONArray 분할
- date_format
- 스프링 리액티브 프로그래밍
- JSONObject 분할
- str_to_date
- Meta Table
- batchInsert
- jar 소스보기
- 문자형을 날짜형으로
- 폐기하기
- 스프링 배치 메타 테이블
- spring reactive programming
- 마리아디비
- org.json
- nonblocking
- JSON 분해
- 스프링 웹플럭스
- git stage
- Today
- Total
ebson
XML 파일을 JSON 문자열로 변환하기 본문
HTTP POST 요청이나 FTP 를 사용해 외부로 데이터를 전송, 전달하는 것은 실무에서 만날 수 있는 흔한 요구사항이다. 이때 사용되는 데이터 포맷은 JSON과 XML 포맷이 있다. HTTP POST 요청으로 데이터를 전달하는 WEB API의 경우에는 XML보다 JSON 형식이 선호된다. FTP로 파일을 업로드할 때는 XML 파일을 사용할 수 있을 것이다. 내가 만난 실무 요구사항의 경우에도 기존에 XML 파일로 특정 위치에 업로드해두던 데이터들을 JSON 형식으로 변환해 제공된 WEB API ENDPOINT으로 HTTP POST 전송하도록 전환하는 것이었다.
기존 소스에서 스프링에서 제공하는 문서 생성 라이브러리를 사용하거나 스트림을 사용해 파일을 생성했기 때문에 어느 위치에서 XML파일이 생성 완료되는지를 알 수 있을 뿐만 아니라 어느 위치로 업로드되는지도 알 수 있어서 완성된 XML 파일도 참고가 가능했지만 XML 파일을 생성 하기까지 복잡하고 방대한 비즈니스 로직을 하나하나 따라가기가 간단한 작업이 아니었다. 시간과 공을 들여 마지막 로직까지 무사히 따라가 JSON 라이브러리에서 제공하는 클래스 안에 모두 담았다고 하더라도, 그 작업 중 한부분에서라도 실수가 없었으리라고 보장할 수도 없었다.
위의 작업을 정확히는 내가 아니라 다른 개발자께서 맡아서 진행 중이셨는데 종종 기존의 XML 파일 업로드 방식대로 그냥 하게 될 것 같다라고 말씀하셨고 그것이 내가 듣기에는 높은 피로도를 표하시는 것 같았다. 그래서 나도 위 작업에 관심을 갖고 관련 소스를 시간과 공을 들여 분석했다. 그 결과 위와 같은 해당 작업의 피로도를 높이는 특징들을 알게 된 것이다. 이 상황을 개선하고자 더 분석해본 결과 위 작업에서 기존 소스는 크게 3가지 방식으로 파일을 생성하고 있었다. org.w3c.dom.Document 를 사용한 방식, org.jdom.Document 을 사용한 방식 그리고 XMLStreamWriter를 사용한 방식이었다.
XML Document를 JSONObject으로 변환하기
문서 라이브러리를 사용하는 두가지 경우가 대부분이었기 때문에 위 방식으로 생성된 XML 파일을 JSON 으로 변환하는 방법부터 찾기로 했다. org.json 라이브러리에서 XML 형식의 문자열을 JSONObject로 변환하는 클래스를 제공 중이었기 때문에 XML 도큐먼트를 문자열로 변환하는 방법을 찾아보았다. org.w3c.dom.Document 의 경우에는 TransformerFactory 클래스와 DOMSource 클래스를 사용해 문자열로 변환할 수 있었다. org.jdom.Document 의 경우에는 XMLOutputter를 사용해 문자열로 변환할 수 있었다. 그리고 특정 태그는 반드시 JSONArray 이어야 했기 때문에 org.json에서 제공하는 XMLParserConfiguration 의 forceList 기능을 적용했다.
public static String XmlDocToJsonStr(org.w3c.dom.Document doc
, org.jdom.Document jDoc
, XMLParserConfiguration XMLParserConfiguration) {
String jsonStr = null;
if(doc != null) {
try {
StringWriter clsOutput = new StringWriter( );
Transformer clsTrans;
clsTrans = TransformerFactory.newInstance( ).newTransformer( );
clsTrans.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION, "no" );
clsTrans.setOutputProperty( OutputKeys.METHOD, "xml" );
clsTrans.setOutputProperty( OutputKeys.INDENT, "yes" );
clsTrans.setOutputProperty( OutputKeys.ENCODING, "UTF-8" );
clsTrans.transform( new DOMSource( doc ), new StreamResult( clsOutput ) );
String xmlStr = clsOutput.toString( );
org.json.JSONObject json = XML.toJSONObject(xmlStr, XMLParserConfiguration);
jsonStr = json.toString();
log.info("XmlToJsonUtil^^XmlDocToJsonStr^^jsonStr : " + jsonStr);
} catch (TransformerConfigurationException e) {
log.error("XmlToJsonUtil^^XmlDocToJsonStr^^TransformerConfigurationException : " + ExceptionUtils.getMessage(e));
} catch (TransformerFactoryConfigurationError e) {
log.error("XmlToJsonUtil^^XmlDocToJsonStr^^TransformerFactoryConfigurationError : " + ExceptionUtils.getMessage(e));
} catch (TransformerException e) {
log.error("XmlToJsonUtil^^XmlDocToJsonStr^^TransformerException : " + ExceptionUtils.getMessage(e));
} catch(Exception e) {
log.error("XmlToJsonUtil^^XmlDocToJsonStr^^Exception : " + ExceptionUtils.getMessage(e));
}
} else if(jDoc != null) {
try {
XMLOutputter xo = new XMLOutputter();
String xmlStr = xo.outputString(jDoc);
org.json.JSONObject json = XML.toJSONObject(xmlStr, XMLParserConfiguration);
jsonStr = json.toString();
log.info("XmlToJsonUtil^^XmlDocToJsonStr^^jsonStr : " + jsonStr);
} catch(Exception e) {
log.error("XmlToJsonUtil^^XmlDocToJsonStr^^Exception : " + ExceptionUtils.getMessage(e));
}
}
return jsonStr;
}
[소스1] XmlToJsonUtil.XmlDocToJsonStr()
Set<String> forceList = new HashSet<>();
forceList.add("Brand");
forceList.add("Category");
forceList.add("Product");
XMLParserConfiguration XMLParserConfiguration = new XMLParserConfiguration().withForceList(forceList);
...
String jsonStr = XmlToJsonUtil.XmlDocToJsonStr(null, xmlDoc, XMLParserConfiguration);
[소스2] XmlDocToJsonStr 사용례
XML Stream을 JSONObject으로 변환하기
XMLStreamWriter를 사용하는 경우에는 XmlOutputFactory.createXMLStreamWriter()의 인자를 FileOutputStream 대신 StringWriter으로 하여 생성함으로서 XML형식의 문자열을 얻을 수 있었고 마찬가지로 org.json.XML 클래스에서 제공하는 toJSONObject 메서드에 XML 향식의 문자열과 XMLParserConfiguration 을 전달함으로서 JSON 형태로 변환할 수 있었다.
public static String XmlStreamWriterToJsonStrWithLocale(StringWriter stringWriter
, XMLParserConfiguration XMLParserConfiguration
, String localeCode
, String job) {
String jsonStr = null;
try {
String xmlStr = stringWriter.toString( );
org.json.JSONObject json = XML.toJSONObject(xmlStr, XMLParserConfiguration);
json.put("job", job); // job 명칭 필수 추가
json.put("locale", localeCode); // locale 필수 추가
jsonStr = json.toString();
log.info("XmlToJsonUtil^^XmlStreamWriterToJsonStr^^jsonStr : " + jsonStr);
} catch (Exception e) {
log.error("XmlToJsonUtil^^XmlStreamWriterToJsonStr^^Exception : " + ExceptionUtils.getMessage(e));
}
return jsonStr;
}
[소스3] XmlToJsonUtil.XmlStreamWriterToJsonStrWithLocale()
public final static XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();
...
Set<String> forceList = new HashSet<>();
forceList.add("items");
XMLParserConfiguration XMLParserConfiguration = new XMLParserConfiguration().withForceList(forceList);
...
StringWriter stringOut = new StringWriter();
XMLStreamWriter xmlStreamWriter;
...
xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(stringOut);
...
String jsonStr = XmlToJsonUtil.XmlStreamWriterToJsonStrWithLocale(stringOut, XMLParserConfiguration, "UK", "ajOB");
[소스4] XmlStreamWriterToJsonStrWithLocale 사용례
위 유틸클래스를 개발해 사용함으로서 기존에 길게는 하나의 배치 잡에 하루가 소요되던 XML 파일을 JSON형태로 변환하는 작업이 하나의 배치 잡에 한시간 내외로 약 800% 효율화할 수 있었다. 그리고 사람이 만들수 있는 실수도 방지할 수 있으면서 API ENDPOINT에서 요청하는 구조로 전달할 수 있게 되었다. 그리고 이렇게 공통 유틸 클래스로 분리해 놓음 으로서 향후 로케일코드 추가, 잡명칭 구분 등과 같은 요구사항에 빠르게 대응할 수 있었다.
피드백
나의 작업이 아니더라도 다른 개발자의 요구사항이 있을 때 고민해 볼 수 있는 작업이 있는 것은 좋은 기회인 것 같다. 실무의 문제를 해결 시도해 볼 기회가 추가로 주어지는 것이기 때문이다. 그리고 이때 무엇보다 중요한 것은 의사소통 역량이라고 생각한다. 의도를 잘 설명하고 결과물을 잘 제시할 수 있어야 상대방에게도 나에게도 도움이 될 수 있다. 블로그 글을 쓰는 것이 의사소통 역량 향상에 도움이 되기를 바란다.