manifold.api.json.JsonTransformer Maven / Gradle / Ivy
/*
* Copyright (c) 2020 - Manifold Systems LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package manifold.api.json;
import manifold.api.fs.IFile;
import manifold.api.host.IManifoldHost;
import manifold.api.json.codegen.*;
import manifold.api.json.codegen.schema.*;
import manifold.json.rt.parser.Token;
import manifold.rt.api.util.Pair;
import manifold.rt.api.Bindings;
import java.util.List;
/**
*
*/
public class JsonTransformer
{
/**
* Makes a tree of structure types reflecting the Bindings.
*
* If the bindings represent a Json Schema format (v.4), the types are transformed according to that schema.
* Otherwise, the bindings are assumed to be a sample Json document whereby types are derived directly
* from the sample. Note when multiple samples exist for the same type eg., array elements, the samples
* are merged and reconciled.
*
* A structure type contains a property member for each name/value pair in the Bindings. A property has the same name as the key and follows these rules:
*
* - If the type of the value is a "simple" type, such as a String or Integer, the type of the property matches the simple type exactly
*
- Otherwise, if the value is a Bindings type, the property type is that of a child structure with the same name as the property and recursively follows these rules
*
- Otherwise, if the value is a List, the property is a List parameterized with the component type, and the component type recursively follows these rules
*
*/
@SuppressWarnings( "unused" )
public static String makeStructureTypes( IManifoldHost host, String nameForStructure, Bindings bindings, AbstractJsonTypeManifold tm,
boolean mutable )
{
JsonStructureType type = (JsonStructureType) transformJsonObject( host, nameForStructure, null, bindings );
StringBuilder sb = new StringBuilder();
type.render( tm, sb, 0, mutable );
return sb.toString();
}
public static IJsonType transformJsonObject( IManifoldHost host, String name, JsonSchemaType parent, Object jsonObj )
{
return transformJsonObject( host, name, null, parent, jsonObj );
}
public static IJsonType transformJsonObject( IManifoldHost host, String name, IFile source, final JsonSchemaType parent, Object jsonObj )
{
if( source == null && parent == null )
{
throw new RuntimeException( "Invalid state, both source and parent are null." );
}
IJsonType type = null;
if( parent != null )
{
type = parent.findChild( name );
}
if( jsonObj instanceof Pair )
{
jsonObj = ((Pair) jsonObj).getSecond();
}
if( jsonObj == null )
{
return DynamicType.instance();
}
else if( jsonObj instanceof Bindings )
{
if( JsonSchemaTransformer.isSchema( (Bindings) jsonObj ) )
{
type = JsonSchemaTransformer.transform( host, name, source, jsonObj );
}
else
{
if( !(type instanceof JsonStructureType) )
{
// handle case for mixed array where component types are different (object and array)
type = new JsonStructureType( parent, source, name, new TypeAttributes() );
}
for( Object k : ((Bindings) jsonObj).keySet() )
{
String key = (String) k;
Object value = ((Bindings) jsonObj).get( key );
Token token = null;
if( value instanceof Pair )
{
token = ((Token[]) ((Pair) value).getFirst())[0];
}
IJsonType memberType = transformJsonObject( host, key, (JsonSchemaType) type, value );
if( memberType != null )
{
((JsonStructureType) type).addMember( key, memberType, token );
}
}
if( parent != null )
{
parent.addChild( name, (IJsonParentType) type );
}
}
}
else if( jsonObj instanceof List )
{
if( !(type instanceof JsonListType) )
{
// handle case for mixed array where component types are different (object and array)
type = new JsonListType( name, source, parent, new TypeAttributes() );
}
IJsonType compType = ((JsonListType) type).getComponentType();
if( !((List) jsonObj).isEmpty() )
{
String nameItem = name + "Item";
int i = 0;
boolean isDissimilar = isDissimilar( (List) jsonObj );
for( Object elem : (List) jsonObj )
{
IJsonType csr = transformJsonObject( host, nameItem + (isDissimilar ? i++ : ""), (JsonSchemaType) type, elem );
if( compType != null && csr != compType && compType != DynamicType.instance() )
{
csr = mergeTypes( compType, csr );
}
compType = csr;
}
}
else if( compType == null )
{
// Empty list implies dynamic component type
compType = DynamicType.instance();
}
((JsonListType) type).setComponentType( compType );
if( parent != null )
{
parent.addChild( name, (IJsonParentType) type );
}
}
else
{
type = JsonBasicType.get( jsonObj );
}
if( parent == null && type instanceof JsonSchemaType )
{
((JsonSchemaType) type).resolveRefs();
JsonSchemaTransformerSession.instance().maybeClear();
}
return type;
}
private static boolean isDissimilar( List jsonObj )
{
Class type = null;
for( Object o : jsonObj )
{
if( type == null )
{
type = o == null ? null : o.getClass();
}
else
{
Class csr = o == null ? null : o.getClass();
if( csr != null && csr != type )
{
return true;
}
type = csr;
}
}
return false;
}
public static IJsonType mergeTypes( IJsonType type1, IJsonType type2 )
{
IJsonType mergedType = mergeTypesNoUnion( type1, type2 );
if( mergedType == null && type1.getParent() instanceof JsonListType )
{
JsonListType listType = (JsonListType) type1.getParent();
JsonUnionType unionType = new JsonUnionType( listType, listType.getFile(), "UnionType", new TypeAttributes() );
unionType.merge( type1 );
unionType.merge( type2 );
mergedType = unionType;
}
if( mergedType != null )
{
return mergedType;
}
// if the existing type is dynamic, override it with a more specific type,
// otherwise the types disagree...
throw new RuntimeException( "Incompatible types: " + type1.getIdentifier() + " vs: " +
(type2 instanceof JsonListType ? "Array: " : "") + type2.getIdentifier() );
}
public static IJsonType mergeTypesNoUnion( IJsonType type1, IJsonType type2 )
{
if( type1 == null )
{
return type2;
}
if( type2 == null )
{
return type1;
}
if( type1.equalsStructurally( type2 ) )
{
return type1;
}
if( type1 == DynamicType.instance() )
{
// Keep the more specific type (Dynamic type is inferred from a 'null', thus the more specific type wins)
return type2;
}
if( type2 == DynamicType.instance() )
{
// Keep the more specific type
return type1;
}
// merge the types
IJsonType mergedType = type1.merge( type2 );
if( mergedType == null )
{
mergedType = type2.merge( type1 );
}
return mergedType;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy