Initial version of QProcessPayload - like QRecordEntity, but for process values. refactoring of QRecordEntity to share logic

This commit is contained in:
2025-03-12 19:58:47 -05:00
parent 0c72210e8e
commit d0768a6981
8 changed files with 460 additions and 143 deletions

View File

@ -0,0 +1,200 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
/*******************************************************************************
** base-class for bean-like classes to represent the fields of a process.
** similar in spirit to QRecordEntity, but for processes.
*******************************************************************************/
public class QProcessPayload
{
private static final QLogger LOG = QLogger.getLogger(QProcessPayload.class);
private static final ListingHash<Class<? extends QProcessPayload>, QRecordEntityField> fieldMapping = new ListingHash<>();
/*******************************************************************************
** Build an entity of this QRecord type from a QRecord
**
*******************************************************************************/
public static <T extends QProcessPayload> T fromProcessState(Class<T> c, ProcessState processState) throws QException
{
try
{
T entity = c.getConstructor().newInstance();
entity.populateFromProcessState(processState);
return (entity);
}
catch(Exception e)
{
throw (new QException("Error building process payload from state.", e));
}
}
/***************************************************************************
**
***************************************************************************/
protected void populateFromProcessState(ProcessState processState)
{
try
{
List<QRecordEntityField> fieldList = getFieldList(this.getClass());
// originalRecordValues = new HashMap<>();
for(QRecordEntityField qRecordEntityField : fieldList)
{
Serializable value = processState.getValues().get(qRecordEntityField.getFieldName());
Object typedValue = qRecordEntityField.convertValueType(value);
qRecordEntityField.getSetter().invoke(this, typedValue);
// originalRecordValues.put(qRecordEntityField.getFieldName(), value);
}
// for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
// {
// List<QRecord> associatedRecords = qRecord.getAssociatedRecords().get(qRecordEntityAssociation.getAssociationAnnotation().name());
// if(associatedRecords == null)
// {
// qRecordEntityAssociation.getSetter().invoke(this, (Object) null);
// }
// else
// {
// List<QRecordEntity> associatedEntityList = new ArrayList<>();
// for(QRecord associatedRecord : CollectionUtils.nonNullList(associatedRecords))
// {
// associatedEntityList.add(QRecordEntity.fromQRecord(qRecordEntityAssociation.getAssociatedType(), associatedRecord));
// }
// qRecordEntityAssociation.getSetter().invoke(this, associatedEntityList);
// }
// }
// for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
// {
// List<QRecord> associatedRecords = qRecord.getAssociatedRecords().get(qRecordEntityAssociation.getAssociationAnnotation().name());
// if(associatedRecords == null)
// {
// qRecordEntityAssociation.getSetter().invoke(this, (Object) null);
// }
// else
// {
// List<QRecordEntity> associatedEntityList = new ArrayList<>();
// for(QRecord associatedRecord : CollectionUtils.nonNullList(associatedRecords))
// {
// associatedEntityList.add(QRecordEntity.fromQRecord(qRecordEntityAssociation.getAssociatedType(), associatedRecord));
// }
// qRecordEntityAssociation.getSetter().invoke(this, associatedEntityList);
// }
// }
}
catch(Exception e)
{
throw (new QRuntimeException("Error building process payload from process state.", e));
}
}
/*******************************************************************************
** Copy the values from this payload into the given process state.
** ALL fields in the entity will be set in the process state.
**
*******************************************************************************/
public void toProcessState(ProcessState processState) throws QRuntimeException
{
try
{
for(QRecordEntityField qRecordEntityField : getFieldList(this.getClass()))
{
processState.getValues().put(qRecordEntityField.getFieldName(), (Serializable) qRecordEntityField.getGetter().invoke(this));
}
}
catch(Exception e)
{
throw (new QRuntimeException("Error populating process state from process payload.", e));
}
}
/***************************************************************************
*
***************************************************************************/
public static Set<Class<?>> allowedFieldTypes()
{
HashSet<Class<?>> classes = new HashSet<>(ReflectiveBeanLikeClassUtils.defaultAllowedTypes());
classes.add(Map.class);
classes.add(List.class);
return (classes);
}
/*******************************************************************************
**
*******************************************************************************/
public static List<QRecordEntityField> getFieldList(Class<? extends QProcessPayload> c)
{
if(!fieldMapping.containsKey(c))
{
List<QRecordEntityField> fieldList = new ArrayList<>();
for(Method possibleGetter : c.getMethods())
{
if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, false, allowedFieldTypes()))
{
Optional<Method> setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
if(setter.isPresent())
{
String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
fieldList.add(new QRecordEntityField(fieldName, possibleGetter, setter.get(), possibleGetter.getReturnType(), null));
}
else
{
LOG.debug("Getter method [" + possibleGetter.getName() + "] does not have a corresponding setter.");
}
}
}
fieldMapping.put(c, fieldList);
}
return (fieldMapping.get(c));
}
}

View File

@ -628,4 +628,15 @@ public class RunBackendStepInput extends AbstractActionInput
{ {
return (QContext.getQInstance().getProcess(getProcessName())); return (QContext.getQInstance().getProcess(getProcessName()));
} }
/***************************************************************************
** return a QProcessPayload subclass instance, with values populated from
** the current process state.
***************************************************************************/
public <T extends QProcessPayload> T getProcessPayload(Class<T> payloadClass) throws QException
{
return QProcessPayload.fromProcessState(payloadClass, getProcessState());
}
} }

View File

@ -445,4 +445,14 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
this.processState.setProcessMetaDataAdjustment(processMetaDataAdjustment); this.processState.setProcessMetaDataAdjustment(processMetaDataAdjustment);
} }
/***************************************************************************
** Update the process state with values from the input processPayload
** subclass instance.
***************************************************************************/
public void setProcessPayload(QProcessPayload processPayload)
{
processPayload.toProcessState(getProcessState());
}
} }

View File

@ -24,20 +24,11 @@ package com.kingsrook.qqq.backend.core.model.data;
import java.io.Serializable; import java.io.Serializable;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@ -49,6 +40,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash; import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.ObjectUtils; import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -325,13 +317,13 @@ public abstract class QRecordEntity
List<QRecordEntityField> fieldList = new ArrayList<>(); List<QRecordEntityField> fieldList = new ArrayList<>();
for(Method possibleGetter : c.getMethods()) for(Method possibleGetter : c.getMethods())
{ {
if(isGetter(possibleGetter)) if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, true))
{ {
Optional<Method> setter = getSetterForGetter(c, possibleGetter); Optional<Method> setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
if(setter.isPresent()) if(setter.isPresent())
{ {
String fieldName = getFieldNameFromGetter(possibleGetter); String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
Optional<QField> fieldAnnotation = getQFieldAnnotation(c, fieldName); Optional<QField> fieldAnnotation = getQFieldAnnotation(c, fieldName);
if(fieldAnnotation.isPresent()) if(fieldAnnotation.isPresent())
@ -378,19 +370,19 @@ public abstract class QRecordEntity
List<QRecordEntityAssociation> associationList = new ArrayList<>(); List<QRecordEntityAssociation> associationList = new ArrayList<>();
for(Method possibleGetter : c.getMethods()) for(Method possibleGetter : c.getMethods())
{ {
if(isGetter(possibleGetter)) if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, true))
{ {
Optional<Method> setter = getSetterForGetter(c, possibleGetter); Optional<Method> setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
if(setter.isPresent()) if(setter.isPresent())
{ {
String fieldName = getFieldNameFromGetter(possibleGetter); String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
Optional<QAssociation> associationAnnotation = getQAssociationAnnotation(c, fieldName); Optional<QAssociation> associationAnnotation = getQAssociationAnnotation(c, fieldName);
if(associationAnnotation.isPresent()) if(associationAnnotation.isPresent())
{ {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Class<? extends QRecordEntity> listTypeParam = (Class<? extends QRecordEntity>) getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType()); Class<? extends QRecordEntity> listTypeParam = (Class<? extends QRecordEntity>) ReflectiveBeanLikeClassUtils.getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType());
associationList.add(new QRecordEntityAssociation(fieldName, possibleGetter, setter.get(), listTypeParam, associationAnnotation.orElse(null))); associationList.add(new QRecordEntityAssociation(fieldName, possibleGetter, setter.get(), listTypeParam, associationAnnotation.orElse(null)));
} }
} }
@ -457,130 +449,4 @@ public abstract class QRecordEntity
} }
/*******************************************************************************
**
*******************************************************************************/
public static String getFieldNameFromGetter(Method getter)
{
String nameWithoutGet = getter.getName().replaceFirst("^get", "");
if(nameWithoutGet.length() == 1)
{
return (nameWithoutGet.toLowerCase(Locale.ROOT));
}
return (nameWithoutGet.substring(0, 1).toLowerCase(Locale.ROOT) + nameWithoutGet.substring(1));
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean isGetter(Method method)
{
if(method.getParameterTypes().length == 0 && method.getName().matches("^get[A-Z].*"))
{
if(isSupportedFieldType(method.getReturnType()) || isSupportedAssociation(method.getReturnType(), method.getAnnotatedReturnType()))
{
return (true);
}
else
{
if(!method.getName().equals("getClass") && method.getAnnotation(QIgnore.class) == null)
{
LOG.debug("Method [" + method.getName() + "] in [" + method.getDeclaringClass().getSimpleName() + "] looks like a getter, but its return type, [" + method.getReturnType().getSimpleName() + "], isn't supported.");
}
}
}
return (false);
}
/*******************************************************************************
**
*******************************************************************************/
private static Optional<Method> getSetterForGetter(Class<? extends QRecordEntity> c, Method getter)
{
String setterName = getter.getName().replaceFirst("^get", "set");
for(Method method : c.getMethods())
{
if(method.getName().equals(setterName))
{
if(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(getter.getReturnType()))
{
return (Optional.of(method));
}
else
{
LOG.info("Method [" + method.getName() + "] looks like a setter for [" + getter.getName() + "], but its parameters, [" + Arrays.toString(method.getParameterTypes()) + "], don't match the getter's return type [" + getter.getReturnType() + "]");
}
}
}
return (Optional.empty());
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean isSupportedFieldType(Class<?> returnType)
{
// todo - more types!!
return (returnType.equals(String.class)
|| returnType.equals(Integer.class)
|| returnType.equals(Long.class)
|| returnType.equals(int.class)
|| returnType.equals(Boolean.class)
|| returnType.equals(boolean.class)
|| returnType.equals(BigDecimal.class)
|| returnType.equals(Instant.class)
|| returnType.equals(LocalDate.class)
|| returnType.equals(LocalTime.class)
|| returnType.equals(byte[].class));
/////////////////////////////////////////////
// note - this list has implications upon: //
// - QFieldType.fromClass //
// - QRecordEntityField.convertValueType //
/////////////////////////////////////////////
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean isSupportedAssociation(Class<?> returnType, AnnotatedType annotatedType)
{
Class<?> listTypeParam = getListTypeParam(returnType, annotatedType);
return (listTypeParam != null && QRecordEntity.class.isAssignableFrom(listTypeParam));
}
/*******************************************************************************
**
*******************************************************************************/
private static Class<?> getListTypeParam(Class<?> listType, AnnotatedType annotatedType)
{
if(listType.equals(List.class))
{
if(annotatedType instanceof AnnotatedParameterizedType apt)
{
AnnotatedType[] annotatedActualTypeArguments = apt.getAnnotatedActualTypeArguments();
for(AnnotatedType annotatedActualTypeArgument : annotatedActualTypeArguments)
{
Type type = annotatedActualTypeArgument.getType();
if(type instanceof Class<?> c)
{
return (c);
}
}
}
}
return (null);
}
} }

View File

@ -28,6 +28,7 @@ import java.math.BigDecimal;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalTime; import java.time.LocalTime;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QValueException; import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -170,6 +171,11 @@ public class QRecordEntityField
{ {
return (ValueUtils.getValueAsByteArray(value)); return (ValueUtils.getValueAsByteArray(value));
} }
if(type.equals(Map.class))
{
return (ValueUtils.getValueAsMap(value));
}
} }
catch(Exception e) catch(Exception e)
{ {

View File

@ -46,6 +46,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock; import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -188,7 +189,7 @@ public class QFieldMetaData implements Cloneable, QMetaDataObject
{ {
try try
{ {
this.name = QRecordEntity.getFieldNameFromGetter(getter); this.name = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(getter);
this.type = QFieldType.fromClass(getter.getReturnType()); this.type = QFieldType.fromClass(getter.getReturnType());
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -0,0 +1,188 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.utils;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.data.QIgnore;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
/*******************************************************************************
** Utilities for bean-like classes (e.g., QRecordEntity, QProcessPayload) that
** use reflection to understand their bean-fields
*******************************************************************************/
public class ReflectiveBeanLikeClassUtils
{
private static final QLogger LOG = QLogger.getLogger(ReflectiveBeanLikeClassUtils.class);
/*******************************************************************************
**
*******************************************************************************/
public static String getFieldNameFromGetter(Method getter)
{
String nameWithoutGet = getter.getName().replaceFirst("^get", "");
if(nameWithoutGet.length() == 1)
{
return (nameWithoutGet.toLowerCase(Locale.ROOT));
}
return (nameWithoutGet.substring(0, 1).toLowerCase(Locale.ROOT) + nameWithoutGet.substring(1));
}
/*******************************************************************************
**
*******************************************************************************/
public static boolean isGetter(Method method, boolean allowAssociations)
{
return isGetter(method, allowAssociations, defaultAllowedTypes());
}
/*******************************************************************************
**
*******************************************************************************/
public static boolean isGetter(Method method, boolean allowAssociations, Collection<Class<?>> allowedTypes)
{
if(method.getParameterTypes().length == 0 && method.getName().matches("^get[A-Z].*"))
{
if(allowedTypes.contains(method.getReturnType()) || (allowAssociations && isSupportedAssociation(method.getReturnType(), method.getAnnotatedReturnType())))
{
return (true);
}
else
{
if(!method.getName().equals("getClass") && method.getAnnotation(QIgnore.class) == null)
{
LOG.debug("Method [" + method.getName() + "] in [" + method.getDeclaringClass().getSimpleName() + "] looks like a getter, but its return type, [" + method.getReturnType().getSimpleName() + "], isn't supported.");
}
}
}
return (false);
}
/*******************************************************************************
**
*******************************************************************************/
public static Optional<Method> getSetterForGetter(Class<?> c, Method getter)
{
String setterName = getter.getName().replaceFirst("^get", "set");
for(Method method : c.getMethods())
{
if(method.getName().equals(setterName))
{
if(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(getter.getReturnType()))
{
return (Optional.of(method));
}
else
{
LOG.info("Method [" + method.getName() + "] looks like a setter for [" + getter.getName() + "], but its parameters, [" + Arrays.toString(method.getParameterTypes()) + "], don't match the getter's return type [" + getter.getReturnType() + "]");
}
}
}
return (Optional.empty());
}
/***************************************************************************
**
***************************************************************************/
public static Collection<Class<?>> defaultAllowedTypes()
{
/////////////////////////////////////////////
// note - this list has implications upon: //
// - QFieldType.fromClass //
// - QRecordEntityField.convertValueType //
/////////////////////////////////////////////
return (Set.of(String.class,
Integer.class,
Long.class,
int.class,
Boolean.class,
boolean.class,
BigDecimal.class,
Instant.class,
LocalDate.class,
LocalTime.class,
byte[].class));
}
/*******************************************************************************
**
*******************************************************************************/
private static boolean isSupportedAssociation(Class<?> returnType, AnnotatedType annotatedType)
{
Class<?> listTypeParam = getListTypeParam(returnType, annotatedType);
return (listTypeParam != null && QRecordEntity.class.isAssignableFrom(listTypeParam));
}
/*******************************************************************************
**
*******************************************************************************/
public static Class<?> getListTypeParam(Class<?> listType, AnnotatedType annotatedType)
{
if(listType.equals(List.class))
{
if(annotatedType instanceof AnnotatedParameterizedType apt)
{
AnnotatedType[] annotatedActualTypeArguments = apt.getAnnotatedActualTypeArguments();
for(AnnotatedType annotatedActualTypeArgument : annotatedActualTypeArguments)
{
Type type = annotatedActualTypeArgument.getType();
if(type instanceof Class<?> c)
{
return (c);
}
}
}
}
return (null);
}
}

View File

@ -22,6 +22,7 @@
package com.kingsrook.qqq.backend.core.utils; package com.kingsrook.qqq.backend.core.utils;
import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
@ -38,6 +39,7 @@ import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import com.kingsrook.qqq.backend.core.context.QContext; import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QValueException; import com.kingsrook.qqq.backend.core.exceptions.QValueException;
@ -1012,4 +1014,37 @@ public class ValueUtils
return defaultIfCannotInfer; return defaultIfCannotInfer;
} }
/***************************************************************************
**
***************************************************************************/
public static Map getValueAsMap(Serializable value)
{
if(value == null)
{
return (null);
}
else if(value instanceof Map<?, ?> map)
{
return (map);
}
else if(value instanceof String string && string.startsWith("{") && string.endsWith("}"))
{
try
{
Map map = JsonUtils.toObject(string, Map.class);
return (map);
}
catch(IOException e)
{
throw new QValueException("Error parsing string to map", e);
}
}
else
{
throw new QValueException("Unrecognized object type in getValueAsMap: " + value.getClass().getSimpleName());
}
}
} }