All Downloads are FREE. Search and download functionalities are using the official Maven repository.

manifold.api.json.JsonTransformer Maven / Gradle / Ivy

There is a newer version: 2024.1.42
Show newest version
/*
 * 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