Git Product home page Git Product logo

Comments (9)

daniel-frak avatar daniel-frak commented on May 26, 2024

That's a very interesting suggestion!
Could you elaborate on how you think it could work, technically?

I don't see how we could feasibly provide the bind command to the public dummies (modules), nor do I expect it to be useful there - let me know if I'm wrong.

I do, however, cautiously see the potential in exposing a method like this:

MyPojo result = dummy4j.expressionResolver().resolveAs("#{some.map.key}", MyPojo.class);

Which would return a result with randomly generated property values.

The question, then, remains on the logic of this method. The only way I see this working is if the first argument always resolved to a Map<String, Object> (and threw an exception otherwise), which would then be resolved to an actual object.

This begs the question, though - what library should we use for the mapping? I don't think we can use SnakeYaml for this, as the "yaml" is already a Java Map by then. Jackson ObjectMapper comes to mind, but it's yet another dependency to take on (and I don't want to bloat Dummy4j too much). We could, of course, do it by hand using reflection - the question is if it's a good idea in this case.

Moreover, how do we match the yaml properties with their POJO counterparts if they don't line up perfectly? It's tempting to expose the external library's annotations for this but, ideally, I would like that library to be replaceable and hidden behind abstractions.

It would also be useful to discuss use cases for this functionality. What is the problem you would currently like to solve with it?

from dummy4j.

jvmlet avatar jvmlet commented on May 26, 2024

I'll start from the end πŸ˜€ The problem I'm trying to solve is to get coherent dummy object, i. e. for the Android OS I want to get one of applicable versions, definitely not Vista.

You are probably right that bind API is useless for standard dummies, but it might be very handy for custom modules.
If you provide API that resolves Map<String, Object> then Jackson mapper is the way to go. You also benefit from allowing your users to setup the custom mapping using Jackson's settings.

To make it optional, I would suggest to extract binder API to separate interface + provide default implementation on top of Jackson, but with compileOnly dependency. Your users will have to add Jackson dependency to their project and configure binder with your default implementation if they are interested in mapping to POJOs.

If user calls bind without setting binder first you throw exception.
If they configured binder and call bind API without adding Jackson dependency they get ClassNotFound exception

from dummy4j.

daniel-frak avatar daniel-frak commented on May 26, 2024

I spent some time thinking about this and I must admit to having some second thoughts.

I think the main argument against this feature is that using the Java API to construct some objects can't be avoided (e.g. when using DateAndTimyDummy or NumeralsDummy). Therefore, the 'binder' couldn't be used as a true alternative to creating a dedicated "generator" class for the objects.

Moreover, at the moment I'm not sure if it would be possible to implement this functionality without modifying the ExpressionResolver. An alternative ExpressionResolver implementation might not use a standard Map at all and we would potentially be forcing more assumptions and functionality on it than might be needed for most people. Someone making a hypothetical cloud-based expression resolver would need to either write their own class binding logic (even if they don't need it) or throw a MethodNotImplementedException, which suggests a Liskov violation on our part.

Thirdly, I want people to focus on using the Java API more than resolving yml keys. I think it's easier to debug actual code than it is to find errors in .yml expressions. While, of course, using Dummy4j (with its default implementations) will inevitably lead to writing .yml, I'm wary that a binder like this would encourage people to try to reimplement (or circumvent) the additional logic that is defined in Dummy4j's module code.

Finally, this is a deceptively complex feature which would potentially alter the core of what Dummy4j is and I'm not yet convinced that it would be useful enough to justify the maintenance overhead. I'm lacking an elevator pitch here.

While I'm not saying it definitely won't happen, I think I'd like to get some more people to comment on this before deciding on implementing it. At the moment, I don't think it's a good fit.

Regarding your use case, the immediate (if not entirely satisfactory) solution could be to store a hardcoded list of keys (List<String> yourList = Arrays.asList("win", "android")) in Java, pick a random value from it (String key = dummy.of(yourList)) and then use it in the resolver (dummy4j.expressionResolver().resolve("#{operating_system." + key + ".name"}").

As for a better solution, I was recently thinking about providing a way to get all keys for a given expression (e.g. dummy4j.expressionResolver().getKeysFor("operating_system") would return ['win', 'android']). That way you wouldn't have to hardcode anything to achieve the desired functionality. It would also be much easier to implement.

What do you think about this as an alternative to the 'binder'? Would a getKeysFor method solve your problem?

from dummy4j.

jvmlet avatar jvmlet commented on May 26, 2024

This is how I implemented it right now.

en:
  faker:
    operating_system:
      win:
        name:
          - Windows
        version:
          - Vista
          - 7
          - 8
          - 10
      android:
        name :
          - Android
        version:
          - Pie
          - Lollilop
      any:
        - win
        - android
String os=resolve("operating_system.any")
OperatingSystemPojo
.builder()
.name(resolve(β€œoperating_system.β€œ+os+".name"))
.version(resolve(β€œoperating_system.β€œ+os+".version"))

The locally unique API from 0.6.1β€”SNAPSHOT would definitely be helpful here.

If you provide API to resolve Map<String, Object> by key I would recursively iterate it by resolving keys and populating appropriate property in pojo.

The yaml should look like this to allow both direct and random access to os:

en:
  faker:
    operating_system:
      win: &win
        name: 
          - Windows
        version:
          - Vista
          - 7
          - 8
          - 10
      android: &android
        name :
          - Android
        version:
          - Pie
          - Lollilop
      any:
        - win:
        <<: *win
        - android:
        <<: *android

Still, this logic is quite generic and might be provided as a binder. I can PR once you expose the required API

from dummy4j.

daniel-frak avatar daniel-frak commented on May 26, 2024

Resolving to Map<String, Object> is one of the things I'd like to avoid at the moment.
What about the getKeysFor solution? It would work somewhat like this:

For a yml file like that:

en:
    operating_system:
      win:
        name: 
          - Windows
        version:
          - Vista
          - 7
          - 8
          - 10
      android:
        name :
          - Android
        version:
          - Pie
          - Lollilop

You could do this:

List<String> operatingSystems = dummy4j.expressionResolver().getKeysFor("operating_system");

String os = dummy4j.of(operatingSystems);

OperatingSystemPojo
    .builder()
    .name(dummy4j.expressionResolver().resolve(β€œ#{operating_system.β€œ + os + ".name}"))
    .version(dummy4j.expressionResolver().resolve(β€œ#{operating_system.β€œ + os + ".version}"))
    .build();

Would something like this work for you?

Edit:
It's late but I just had a quick thought that you could implement a binder like this as an extension without touching Dummy4j's core and without needing to expose any Map<String, Object> methods. Here's a quick & dirty example:

// Super quick and super dirty, but should get the point across
public class Binder {

    private final Dummy4j dummy4j;

    public Binder(Dummy4j dummy4j) {
        this.dummy4j = dummy4j;
    }

    public <T> T bind(String keyPrefix, Class<T> targetClass)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        T instance = targetClass.getConstructor().newInstance();
        List<Method> setters = getSetters(targetClass);

        for (String fieldName : getFieldNames(targetClass)) {
            Method setter = getSetter(setters, fieldName);
            String lowerCaseFirstLetterFieldName = fieldName.substring(0, 1).toLowerCase() + fieldName.substring(1);
            setter.invoke(instance, resolveField(keyPrefix, lowerCaseFirstLetterFieldName));
        }

        return instance;
    }

    private <T> List<Method> getSetters(Class<T> targetClass) {
        return Arrays.stream(targetClass.getDeclaredMethods())
                .filter(m -> m.getName().startsWith("set"))
                .collect(Collectors.toList());
    }

    private <T> List<String> getFieldNames(Class<T> targetClass) {
        return Arrays.stream(targetClass.getDeclaredMethods())
                .filter(m -> m.getName().startsWith("get"))
                .map(m -> m.getName().substring(3))
                .collect(Collectors.toList());
    }

    private Method getSetter(List<Method> setters, String fieldName) {
        String setterName = "set" + fieldName;
        
        return setters.stream()
                .filter(s -> s.getName().equals(setterName))
                .findFirst()
                .orElseThrow(() -> new RuntimeException("Setter not found: " + setterName));
    }

    private String resolveField(String keyPrefix, String fieldName) {
        return dummy4j.expressionResolver.resolve("#{" + keyPrefix + "." + fieldName + "}");
    }
}

Then usage:

OperatingSystem result = binder.bind("operating_system.win", OperatingSystem.class);

You could use something like this in your code and then perhaps use the proposed getKeysFor method to get the win/android part of the expression.

Just thought I'd put this up here as a late-night inspiration. Still though, would a getKeysFor method be a good enough solution for your needs, given all of the above?

from dummy4j.

jvmlet avatar jvmlet commented on May 26, 2024

I would rather load the yaml file by myself and iterate it by calling your resolve API for keys 😁
It will produce the Map<String, Object> and then I bind with single line of Jackson

from dummy4j.

daniel-frak avatar daniel-frak commented on May 26, 2024

Not sure I understand, but whatever gets you to whatever you need to achieve :)

In summary, I'm not going to be implementing the binding functionality at the moment. I'm still willing to consider it if you (or someone else) make a good argument for it, though.
On the other hand, I'm considering adding a getKeysFor method which would return "all subkeys directly within a given key" (so for operating_system that would be [win, android]). If you think a method like that could be useful, let me know.

Should I close this issue, or do you think there is more to discuss?

from dummy4j.

jvmlet avatar jvmlet commented on May 26, 2024

I prefer to leave it opened to allow others to find it and express their opinions

from dummy4j.

daniel-frak avatar daniel-frak commented on May 26, 2024

Ok, I'll leave it open then.

Feel free to comment if anything else comes to your mind on this topic :)

from dummy4j.

Related Issues (9)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.