- JS: 배열 내부 객체(children) 및 타입(Render, Save) 동기화 로직 개선 - 스칼라 배열 예제 추가 및 Swagger 타입 매핑 로직 구현
This commit is contained in:
@@ -248,9 +248,11 @@
|
|||||||
if (row.description && row.description.value) obj.properties[name].description = row.description.value;
|
if (row.description && row.description.value) obj.properties[name].description = row.description.value;
|
||||||
if (row.name && row.name.locked) obj.properties[name]['x-djb-gw'] = true;
|
if (row.name && row.name.locked) obj.properties[name]['x-djb-gw'] = true;
|
||||||
} else if (row.type.value === 'array') {
|
} else if (row.type.value === 'array') {
|
||||||
|
const itemType = (row.itemsType && row.itemsType.value) || 'string';
|
||||||
obj.properties[name] = {
|
obj.properties[name] = {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: { type: (row.itemsType && row.itemsType.value) || 'string' }
|
// array<object>(GW GRID): 자식 행을 items.properties 로 직렬화. (누락 시 저장 왕복에서 자식 유실)
|
||||||
|
items: itemType === 'object' ? schemaArrayToObject(row.children || []) : { type: itemType }
|
||||||
};
|
};
|
||||||
if (row.description && row.description.value) obj.properties[name].description = row.description.value;
|
if (row.description && row.description.value) obj.properties[name].description = row.description.value;
|
||||||
if (row.name && row.name.locked) obj.properties[name]['x-djb-gw'] = true;
|
if (row.name && row.name.locked) obj.properties[name]['x-djb-gw'] = true;
|
||||||
@@ -672,6 +674,7 @@
|
|||||||
const isLocked = row.name.locked;
|
const isLocked = row.name.locked;
|
||||||
const isObject = row.type.value === 'object';
|
const isObject = row.type.value === 'object';
|
||||||
const isArray = row.type.value === 'array';
|
const isArray = row.type.value === 'array';
|
||||||
|
const isArrayOfObject = isArray && (row.itemsType && row.itemsType.value) === 'object';
|
||||||
const expandedKey = `${path}.__expanded`;
|
const expandedKey = `${path}.__expanded`;
|
||||||
const isExpanded = state[expandedKey] !== false;
|
const isExpanded = state[expandedKey] !== false;
|
||||||
|
|
||||||
@@ -681,7 +684,7 @@
|
|||||||
<td class="py-2 px-2 text-center">${isLocked ? '<span class="text-slate-400 text-xs" title="게이트웨이 동기화">🔒</span>' : ''}</td>
|
<td class="py-2 px-2 text-center">${isLocked ? '<span class="text-slate-400 text-xs" title="게이트웨이 동기화">🔒</span>' : ''}</td>
|
||||||
<td class="py-2 px-2">
|
<td class="py-2 px-2">
|
||||||
<div class="flex items-center gap-1" style="padding-left:${depth * 18}px;">
|
<div class="flex items-center gap-1" style="padding-left:${depth * 18}px;">
|
||||||
${isObject || isArray
|
${isObject || isArrayOfObject
|
||||||
? `<button data-toggle-expand="${path}" class="w-4 h-4 text-slate-400 hover:text-slate-700">${isExpanded ? '▾' : '▸'}</button>`
|
? `<button data-toggle-expand="${path}" class="w-4 h-4 text-slate-400 hover:text-slate-700">${isExpanded ? '▾' : '▸'}</button>`
|
||||||
: `<span class="w-4 inline-block"></span>`}
|
: `<span class="w-4 inline-block"></span>`}
|
||||||
<span class="text-xs text-slate-400 mr-1">${isObject ? '{}' : isArray ? '[]' : '·'}</span>
|
<span class="text-xs text-slate-400 mr-1">${isObject ? '{}' : isArray ? '[]' : '·'}</span>
|
||||||
@@ -712,7 +715,7 @@
|
|||||||
isArray
|
isArray
|
||||||
? `<div class="flex gap-1 items-center">
|
? `<div class="flex gap-1 items-center">
|
||||||
<span class="text-xs text-slate-400">items:</span>
|
<span class="text-xs text-slate-400">items:</span>
|
||||||
${selectInput((row.itemsType && row.itemsType.value) || 'string', ['string', 'integer', 'number', 'boolean'], `data-path="${path}.itemsType.value"`, { extra: 'text-xs' })}
|
${selectInput((row.itemsType && row.itemsType.value) || 'string', ['string', 'integer', 'number', 'boolean', 'object'], `data-path="${path}.itemsType.value" data-rerender="step3"`, { extra: 'text-xs' })}
|
||||||
</div>`
|
</div>`
|
||||||
: input(row.example.value, `data-path="${path}.example.value"`, { placeholder: '예제값' })}
|
: input(row.example.value, `data-path="${path}.example.value"`, { placeholder: '예제값' })}
|
||||||
</td>
|
</td>
|
||||||
@@ -724,8 +727,8 @@
|
|||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// children
|
// children (object + array<object>(GRID) 공통)
|
||||||
if (isObject && isExpanded) {
|
if ((isObject || isArrayOfObject) && isExpanded) {
|
||||||
if (row.children && row.children.length) {
|
if (row.children && row.children.length) {
|
||||||
html += renderSchemaRows(row.children, `${path}.children`, depth + 1, seq);
|
html += renderSchemaRows(row.children, `${path}.children`, depth + 1, seq);
|
||||||
}
|
}
|
||||||
@@ -1683,7 +1686,12 @@
|
|||||||
example: wrap(p.example == null ? '' : p.example), children: null
|
example: wrap(p.example == null ? '' : p.example), children: null
|
||||||
};
|
};
|
||||||
if (t === 'object') row.children = schemaToRows(p);
|
if (t === 'object') row.children = schemaToRows(p);
|
||||||
else if (t === 'array') row.itemsType = wrap((p.items && p.items.type) || 'string');
|
else if (t === 'array') {
|
||||||
|
var itemType = (p.items && p.items.type) || 'string';
|
||||||
|
row.itemsType = wrap(itemType);
|
||||||
|
// array<object>(GW GRID): items.properties 를 자식 행으로 복원. (누락 시 자식 필드 유실)
|
||||||
|
if (itemType === 'object') row.children = schemaToRows(p.items);
|
||||||
|
}
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
function schemaToRows(schema) {
|
function schemaToRows(schema) {
|
||||||
|
|||||||
@@ -280,6 +280,17 @@ public class ApiSpecManService {
|
|||||||
ObjectNode arrayItem = arrayNode.addObject();
|
ObjectNode arrayItem = arrayNode.addObject();
|
||||||
nodeMap.put(item.getLoutitempath(), arrayItem);
|
nodeMap.put(item.getLoutitempath(), arrayItem);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// JSON 레이아웃의 반복 항목(itemType="Array"). 자식이 있으면 객체배열, 없으면 스칼라배열.
|
||||||
|
case "ARRAY":
|
||||||
|
ArrayNode arrNode = parentNode.putArray(item.getLoutItemName());
|
||||||
|
if (hasChildItems(items, item)) {
|
||||||
|
ObjectNode arrObj = arrNode.addObject();
|
||||||
|
nodeMap.put(item.getLoutitempath(), arrObj);
|
||||||
|
} else {
|
||||||
|
addScalarToArray(arrNode, item);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,6 +368,54 @@ public class ApiSpecManService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 지정 항목이 하위(자식) 항목을 가지는지 여부.
|
||||||
|
* <p>JSON 레이아웃에서 배열/객체는 FIELD 노드(반복='*' → itemType="Array")로 저장되고
|
||||||
|
* 자식 항목은 loutitempath 접두어 + depth+1 로 표현된다. 자식 유무로 객체배열/스칼라배열을 구분한다.
|
||||||
|
*/
|
||||||
|
private boolean hasChildItems(List<LayoutItemUI> items, LayoutItemUI parent) {
|
||||||
|
String parentPath = parent.getLoutitempath();
|
||||||
|
if (parentPath == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String prefix = parentPath + ".";
|
||||||
|
int childDepth = parent.getLoutItemDepth() + 1;
|
||||||
|
for (LayoutItemUI it : items) {
|
||||||
|
String p = it.getLoutitempath();
|
||||||
|
if (p != null && p.startsWith(prefix) && it.getLoutItemDepth() == childDepth) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 스칼라 배열(자식 없는 Array)의 예시 요소 1개를 배열에 추가한다. addFieldValue 의 배열 버전. */
|
||||||
|
private void addScalarToArray(ArrayNode arrayNode, LayoutItemUI item) {
|
||||||
|
String dataType = item.getLoutItemDataType();
|
||||||
|
String defaultValue = item.getLoutItemDefault();
|
||||||
|
boolean isInteger = isIntegerType(dataType);
|
||||||
|
|
||||||
|
if (isNumericType(dataType)) {
|
||||||
|
try {
|
||||||
|
if (StringUtils.isNotEmpty(defaultValue)) {
|
||||||
|
if (isInteger) {
|
||||||
|
arrayNode.add(Integer.parseInt(defaultValue));
|
||||||
|
} else {
|
||||||
|
arrayNode.add(Double.parseDouble(defaultValue));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arrayNode.add(isInteger ? 0 : 0.0);
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
arrayNode.add(isInteger ? 0 : 0.0);
|
||||||
|
}
|
||||||
|
} else if ("BOOLEAN".equalsIgnoreCase(dataType)) {
|
||||||
|
arrayNode.add(StringUtils.isNotEmpty(defaultValue) ? Boolean.parseBoolean(defaultValue) : false);
|
||||||
|
} else {
|
||||||
|
arrayNode.add(StringUtils.isNotEmpty(defaultValue) ? defaultValue : "sample_" + item.getLoutItemName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isIntegerType(String dataType) {
|
private boolean isIntegerType(String dataType) {
|
||||||
if (dataType == null) return false;
|
if (dataType == null) return false;
|
||||||
switch (dataType.toUpperCase()) {
|
switch (dataType.toUpperCase()) {
|
||||||
@@ -661,6 +720,19 @@ public class ApiSpecManService {
|
|||||||
ObjectNode arrayItemProps = arrayItemSchema.putObject("properties");
|
ObjectNode arrayItemProps = arrayItemSchema.putObject("properties");
|
||||||
depthMap.put(item.getLoutItemDepth(), arrayItemProps);
|
depthMap.put(item.getLoutItemDepth(), arrayItemProps);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// JSON 레이아웃의 반복 항목(itemType="Array"). 자식이 있으면 객체배열(items.properties), 없으면 스칼라배열.
|
||||||
|
case "ARRAY":
|
||||||
|
ObjectNode arrSchema = parentNode.putObject(item.getLoutItemName());
|
||||||
|
arrSchema.put("type", "array");
|
||||||
|
ObjectNode arrItems = arrSchema.putObject("items");
|
||||||
|
if (hasChildItems(layoutItems, item)) {
|
||||||
|
arrItems.put("type", "object");
|
||||||
|
depthMap.put(item.getLoutItemDepth(), arrItems.putObject("properties"));
|
||||||
|
} else {
|
||||||
|
arrItems.put("type", mapSwaggerType(item.getLoutItemDataType()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user